diff --git a/.gitignore b/.gitignore index d4e83c1..c9baa43 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ *.userprefs # Build results +Temp/ [Dd]ebug/ [Dd]ebugPublic/ [Rr]eleases/ @@ -64,6 +65,9 @@ artifacts/ *.pidb *.svclog *.scc +*.ipdb +*.iobj +*.lastcodeanalysissucceeded # Chutzpah Test files _Chutzpah* diff --git a/Build/Release/x64/repacls.exe b/Build/Release/x64/repacls.exe index db63e69..42cf8cd 100644 Binary files a/Build/Release/x64/repacls.exe and b/Build/Release/x64/repacls.exe differ diff --git a/Build/Release/x86/repacls.exe b/Build/Release/x86/repacls.exe index 9a33eb9..865f5e9 100644 Binary files a/Build/Release/x86/repacls.exe and b/Build/Release/x86/repacls.exe differ diff --git a/Build/Repacls.zip b/Build/Repacls.zip index 42c35dd..9aba757 100644 Binary files a/Build/Repacls.zip and b/Build/Repacls.zip differ diff --git a/Build/build.cmd b/Build/build.cmd index a3be320..67a652e 100644 --- a/Build/build.cmd +++ b/Build/build.cmd @@ -10,6 +10,9 @@ set LIBNAME=Repacls set LIBURL=https://github.com/NoMoreFood/Repacls :: do cleanup +DEL "%~dp0*.iobj" /F /S /Q >NUL 2>&1 +DEL "%~dp0*.ipdb" /F /S /Q >NUL 2>&1 +DEL "%~dp0lastcodeanalysissucceeded" /F /S /Q >NUL 2>&1 RD /S /Q "%~dp0..\.vs" >NUL 2>&1 RD /S /Q "%~dp0..\Temp" >NUL 2>&1 RD /S /Q "%~dp0Debug" >NUL 2>&1 diff --git a/Functions.h b/Functions.h index 7e33172..d46fd9c 100644 --- a/Functions.h +++ b/Functions.h @@ -6,7 +6,7 @@ // helper functions VOID EnablePrivs(); -const PSID GetSidFromName(std::wstring & sAccountName); +PSID GetSidFromName(std::wstring & sAccountName); std::wstring GetNameFromSid(const PSID tSid, bool * bMarkAsOrphan = nullptr); std::wstring GetNameFromSidEx(const PSID tSid, bool * bMarkAsOrphan = nullptr); std::wstring GetDomainNameFromSid(const PSID tSid); diff --git a/Helpers.cpp b/Helpers.cpp index a50af17..bc58cfe 100644 --- a/Helpers.cpp +++ b/Helpers.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include @@ -18,7 +18,7 @@ #include "Operation.h" #include "Functions.h" -const PSID GetSidFromName(std::wstring & sAccountName) +PSID GetSidFromName(std::wstring & sAccountName) { // for caching static std::shared_mutex oMutex; @@ -27,7 +27,7 @@ const PSID GetSidFromName(std::wstring & sAccountName) // scope lock for thread safety { std::shared_lock oLock(oMutex); - std::map::iterator oInteractor = oNameToSidLookup.find(sAccountName); + const auto oInteractor = oNameToSidLookup.find(sAccountName); if (oInteractor != oNameToSidLookup.end()) { return oInteractor->second; @@ -59,7 +59,7 @@ const PSID GetSidFromName(std::wstring & sAccountName) // reallocate memory and copy sid to a smaller part of memory and // then add the sid to the cache map - PSID tSid = (PSID)memcpy(malloc(iSidSize), tSidFromName, iSidSize); + auto tSid = (PSID) memcpy(malloc(iSidSize), tSidFromName, iSidSize); // scope lock for thread safety { @@ -83,12 +83,12 @@ std::wstring GetNameFromSid(const PSID tSid, bool * bMarkAsOrphan) // scope lock for thread safety { std::shared_lock oLock(oMutex); - std::map::iterator oInteractor = oSidToNameLookup.find(tSid); + auto oInteractor = oSidToNameLookup.find(tSid); if (oInteractor != oSidToNameLookup.end()) { // if blank that means the account has no associated same // and likely is an orphan - if (oInteractor->second == L"" && + if (oInteractor->second.empty() && bMarkAsOrphan != NULL) *bMarkAsOrphan = true; // return the found full name @@ -126,7 +126,7 @@ std::wstring GetNameFromSid(const PSID tSid, bool * bMarkAsOrphan) // copy the sid for storage in our cache table const DWORD iSidLength = GetLengthSid(tSid); - PSID tSidCopy = (PSID)memcpy(malloc(iSidLength), tSid, iSidLength); + auto tSidCopy = (PSID)memcpy(malloc(iSidLength), tSid, iSidLength); // scope lock for thread safety { @@ -142,7 +142,7 @@ std::wstring GetNameFromSidEx(const PSID tSid, bool * bMarkAsOrphan) { // if sid is resolvable then return the account name std::wstring sName = GetNameFromSid(tSid, bMarkAsOrphan); - if (sName != L"") return sName; + if (!sName.empty()) return sName; // if sid is unresolvable then return sid in string form WCHAR * sSidBuf; @@ -159,7 +159,7 @@ std::wstring GetDomainNameFromSid(const PSID tSid) // sometimes the domain will be returned as DOMAIN\DOMAIN instead // of just DOMAIN\ so lets trim off any excess characters - std::wstring::size_type nSlash = sDomainName.find(L"\\"); + const std::wstring::size_type nSlash = sDomainName.find(L'\\'); if (nSlash != std::wstring::npos) sDomainName.erase(nSlash + 1); return sDomainName; } @@ -173,7 +173,7 @@ std::wstring GenerateInheritanceFlags(DWORD iCurrentFlags) if (INHERIT_ONLY_ACE & iCurrentFlags) sFlags += L"Inherit Only;"; // handle the empty case or trim off the trailing semicolon - if (sFlags.size() == 0) sFlags = L"None"; + if (sFlags.empty()) sFlags = L"None"; else sFlags.pop_back(); // return the calculated string @@ -219,7 +219,7 @@ std::wstring GenerateAccessMask(DWORD iCurrentMask) // loop through the mask and construct of string of the names std::wstring sMaskList; - for (int iMaskEntry = 0; iMaskEntry < _countof(MaskDefinitions) && iCurrentMask > 0; iMaskEntry++) + for (unsigned int iMaskEntry = 0; iMaskEntry < _countof(MaskDefinitions) && iCurrentMask > 0; ++iMaskEntry) { if ((MaskDefinitions[iMaskEntry].Mask & iCurrentMask) == MaskDefinitions[iMaskEntry].Mask) { @@ -235,7 +235,7 @@ std::wstring GenerateAccessMask(DWORD iCurrentMask) } // handle the empty case or trim off the trailing semicolon - if (sMaskList.size() == 0) sMaskList = L"None"; + if (sMaskList.empty()) sMaskList = L"None"; else sMaskList.pop_back(); // return the calculated string @@ -255,7 +255,7 @@ VOID EnablePrivs() // get the current user sid out of the token BYTE aBuffer[sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE]; - PTOKEN_USER tTokenUser = (PTOKEN_USER)(aBuffer); + auto tTokenUser = (PTOKEN_USER)(aBuffer); DWORD iBytesFilled = 0; if (GetTokenInformation(hToken, TokenUser, tTokenUser, sizeof(aBuffer), &iBytesFilled) == 0) { @@ -266,7 +266,7 @@ VOID EnablePrivs() WCHAR * sPrivsToSet[] = { SE_RESTORE_NAME, SE_BACKUP_NAME, SE_TAKE_OWNERSHIP_NAME, SE_CHANGE_NOTIFY_NAME }; - for (int i = 0; i < sizeof(sPrivsToSet) / sizeof(WCHAR *); i++) + for (auto& i : sPrivsToSet) { // populate the privilege adjustment structure TOKEN_PRIVILEGES tPrivEntry; @@ -274,10 +274,10 @@ VOID EnablePrivs() tPrivEntry.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // translate the privilege name into the binary representation - if (LookupPrivilegeValue(NULL, sPrivsToSet[i], + if (LookupPrivilegeValue(NULL, i, &tPrivEntry.Privileges[0].Luid) == 0) { - wprintf(L"ERROR: Could not lookup privilege: %s\n", sPrivsToSet[i]); + wprintf(L"ERROR: Could not lookup privilege: %s\n", i); continue; } @@ -306,9 +306,9 @@ VOID EnablePrivs() // convert the privilege name to a unicode string format LSA_UNICODE_STRING sPrivilege; - sPrivilege.Buffer = sPrivsToSet[i]; - sPrivilege.Length = (USHORT)(wcslen(sPrivsToSet[i]) * sizeof(WCHAR)); - sPrivilege.MaximumLength = (USHORT)((wcslen(sPrivsToSet[i]) + 1) * sizeof(WCHAR)); + sPrivilege.Buffer = i; + sPrivilege.Length = (USHORT)(wcslen(i) * sizeof(WCHAR)); + sPrivilege.MaximumLength = (USHORT)((wcslen(i) + 1) * sizeof(WCHAR)); // attempt to add the account to policy if ((iResult = LsaAddAccountRights(hPolicyHandle, @@ -316,7 +316,7 @@ VOID EnablePrivs() { LsaClose(hPolicyHandle); wprintf(L"ERROR: Privilege '%s' was not able to be added with error '%lu'\n", - sPrivsToSet[i], LsaNtStatusToWinError(iResult)); + i, LsaNtStatusToWinError(iResult)); continue; } @@ -326,14 +326,14 @@ VOID EnablePrivs() if (AdjustTokenPrivileges(hToken, FALSE, &tPrivEntry, sizeof(TOKEN_PRIVILEGES), NULL, NULL) == 0 || GetLastError() != ERROR_NOT_ALL_ASSIGNED) { - wprintf(L"ERROR: Could not adjust privilege: %s\n", sPrivsToSet[i]); + wprintf(L"ERROR: Could not adjust privilege: %s\n", i); continue; } // error if not all items were assigned if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { - wprintf(L"ERROR: Could not enable privilege: %s\n", sPrivsToSet[i]); + wprintf(L"ERROR: Could not enable privilege: %s\n", i); } } @@ -347,7 +347,7 @@ HANDLE RegisterFileHandle(HANDLE hFile, std::wstring sOperation) static std::map> oFileLookup; // do a reverse lookup on the file name - DWORD iSize = GetFinalPathNameByHandle(hFile, NULL, 0, VOLUME_NAME_NT); + auto iSize = (size_t) GetFinalPathNameByHandle(hFile, NULL, 0, VOLUME_NAME_NT); // create a string that can accommodate that size (plus null terminating character) std::wstring sPath; @@ -365,7 +365,7 @@ HANDLE RegisterFileHandle(HANDLE hFile, std::wstring sOperation) sPath.resize(iSize); // if the handle already exists, then use that one if the parameters match - std::map>::iterator oFile = oFileLookup.find(sPath); + auto oFile = oFileLookup.find(sPath); if (oFile != oFileLookup.end()) { if (oFileLookup[sPath].second == sOperation) @@ -389,10 +389,10 @@ HANDLE RegisterFileHandle(HANDLE hFile, std::wstring sOperation) std::wstring GetAntivirusStateDescription() { // initialize COM for checking the antivirus status - HRESULT hResult = CoInitializeEx(0, COINIT_APARTMENTTHREADED); + const HRESULT hResult = CoInitializeEx(0, COINIT_APARTMENTTHREADED); if (hResult != S_OK && hResult != S_FALSE) { - return false; + return L"Unknown"; } // assume not installed by default @@ -477,7 +477,7 @@ BOOL WriteToFile(std::wstring & sStringToWrite, HANDLE hFile) } // allocate and do the conversion - BYTE * sString = (BYTE *) malloc(iChars); + auto* sString = (BYTE *) malloc(iChars); iChars = WideCharToMultiByte(CP_UTF8, 0, sStringToWrite.c_str(), (int) sStringToWrite.length(), (LPSTR) sString, iChars, NULL, NULL); diff --git a/InputOutput.h b/InputOutput.h index 2fd8bce..46a10e3 100644 --- a/InputOutput.h +++ b/InputOutput.h @@ -3,6 +3,8 @@ #include #include +#include "OperationLog.h" + class InputOutput { private: @@ -45,6 +47,12 @@ class InputOutput return iMaxThreads; } + static bool & Log() + { + static bool bLog = false; + return bLog; + } + static std::vector & ScanPaths() { static std::vector vScanPaths; @@ -54,13 +62,13 @@ class InputOutput static void AddFile(const std::wstring & sLine) { // discover the long file name prefix so we can subtract it from the display path - static std::wstring sPrefix = L"FILE: "; + static std::wstring sPrefix; static size_t iPrefix = (size_t) -1; if (iPrefix == (size_t) -1) { const std::wstring sUnc = L"\\??\\UNC\\"; const std::wstring sLocal = L"\\??\\"; - if (sLine.compare(0, sUnc.size(), sUnc.c_str()) == 0) { iPrefix = sUnc.size(); sPrefix += L"\\\\"; } + if (sLine.compare(0, sUnc.size(), sUnc.c_str()) == 0) { iPrefix = sUnc.size(); sPrefix = L"\\\\"; } else if (sLine.compare(0, sLocal.size(), sLocal.c_str()) == 0) iPrefix = sLocal.size(); else iPrefix = 0; } @@ -69,31 +77,46 @@ class InputOutput GetDetail() = L""; } - static void AddInfo(const std::wstring & sLine, std::wstring sPart, bool bMandatory = false) + static void AddInfo(const std::wstring & sLine, const std::wstring & sPart, bool bMandatory = false) { + if (Log()) + { + OperationLog::LogFileItem(L"INFO", GetFileName(), sLine + ((sPart.empty()) ? L"" : L" in " + sPart)); + } + if (!InQuietMode() || bMandatory) { - GetDetail() += L" INFO: " + sLine + ((sPart == L"") ? L"" : L" in " + sPart) + L"\n"; + GetDetail() += L" INFO: " + sLine + ((sPart.empty()) ? L"" : L" in " + sPart) + L"\n"; } } static void AddWarning(const std::wstring & sLine) { + if (Log()) + { + OperationLog::LogFileItem(L"WARNING", GetFileName(), sLine); + } + GetDetail() += L" WARNING: " + sLine + L"\n"; } static void AddError(const std::wstring & sLine, const std::wstring & sExtended = L"") { + if (Log()) + { + OperationLog::LogFileItem(L"ERROR", GetFileName(), sLine); + } + GetDetail() += L" ERROR: " + sLine + L"\n"; - if (sExtended != L"") GetDetail() += L" ERROR DETAIL: " + sExtended + L"\n"; + if (!sExtended.empty()) GetDetail() += L" ERROR DETAIL: " + sExtended + L"\n"; } static void WriteToScreen() { - // to to screen if there is anything to write - if (GetFileName().size() > 0 && GetDetail().size() > 0) + // output to screen if there is anything to write + if (!GetFileName().empty() && !GetDetail().empty()) { - wprintf(L"%s", (GetFileName() + GetDetail()).c_str()); + wprintf(L"FILE: %s", (GetFileName() + GetDetail()).c_str()); } // clear out buffer now that it's printed diff --git a/Main.cpp b/Main.cpp index 8d5a983..691c4aa 100644 --- a/Main.cpp +++ b/Main.cpp @@ -1,11 +1,12 @@ -#define UMDF_USING_NTSTATUS +#define UMDF_USING_NTSTATUS #include #include -#include +#include #include #include -#include +#include +#include #include #include @@ -46,7 +47,7 @@ bool bFetchGroup = false; void AnalyzeSecurity(ObjectEntry & oEntry) { // update file counter - iFilesScanned++; + ++iFilesScanned; // print out file name InputOutput::AddFile(oEntry.Name); @@ -101,42 +102,41 @@ void AnalyzeSecurity(ObjectEntry & oEntry) DWORD iSpecialCommitMergeFlags = 0; // loop through the instruction list - for (std::vector::iterator oOperation = oOperationList.begin(); - oOperation != oOperationList.end(); oOperation++) + for (auto& oOperation : oOperationList) { // skip if this operation does not apply to the root/children based on the operation - if ((*oOperation)->AppliesToRootOnly && !oEntry.IsRoot || - (*oOperation)->AppliesToChildrenOnly && oEntry.IsRoot) + if (oOperation->AppliesToRootOnly && !oEntry.IsRoot || + oOperation->AppliesToChildrenOnly && oEntry.IsRoot) { continue; } // merge any special commit flags - iSpecialCommitMergeFlags |= (*oOperation)->SpecialCommitFlags; + iSpecialCommitMergeFlags |= oOperation->SpecialCommitFlags; - if ((*oOperation)->AppliesToObject) + if (oOperation->AppliesToObject) { - (*oOperation)->ProcessObjectAction(oEntry); + oOperation->ProcessObjectAction(oEntry); } - if ((*oOperation)->AppliesToDacl) + if (oOperation->AppliesToDacl) { - bDaclIsDirty |= (*oOperation)->ProcessAclAction(L"DACL", oEntry, tAclDacl, bDaclCleanupRequired); + bDaclIsDirty |= oOperation->ProcessAclAction(L"DACL", oEntry, tAclDacl, bDaclCleanupRequired); } - if ((*oOperation)->AppliesToSacl) + if (oOperation->AppliesToSacl) { - bSaclIsDirty |= (*oOperation)->ProcessAclAction(L"SACL", oEntry, tAclSacl, bSaclCleanupRequired); + bSaclIsDirty |= oOperation->ProcessAclAction(L"SACL", oEntry, tAclSacl, bSaclCleanupRequired); } - if ((*oOperation)->AppliesToOwner) + if (oOperation->AppliesToOwner) { - bOwnerIsDirty |= (*oOperation)->ProcessSidAction(L"OWNER", oEntry, tOwnerSid, bOwnerCleanupRequired); + bOwnerIsDirty |= oOperation->ProcessSidAction(L"OWNER", oEntry, tOwnerSid, bOwnerCleanupRequired); } - if ((*oOperation)->AppliesToGroup) + if (oOperation->AppliesToGroup) { - bGroupIsDirty |= (*oOperation)->ProcessSidAction(L"GROUP", oEntry, tGroupSid, bGroupCleanupRequired); + bGroupIsDirty |= oOperation->ProcessSidAction(L"GROUP", oEntry, tGroupSid, bGroupCleanupRequired); } - if ((*oOperation)->AppliesToSd) + if (oOperation->AppliesToSd) { - if ((*oOperation)->ProcessSdAction(oEntry.Name, oEntry, tDesc, bDescCleanupRequired)) + if (oOperation->ProcessSdAction(oEntry.Name, oEntry, tDesc, bDescCleanupRequired)) { // cleanup previous operations if necessary if (bDaclCleanupRequired) { LocalFree(tAclDacl); bDaclCleanupRequired = false; }; @@ -158,11 +158,9 @@ void AnalyzeSecurity(ObjectEntry & oEntry) GetSecurityDescriptorControl(tDesc, &tControl, &tRevisionInfo); // convert inheritance bits to the special flags that control inheritance - iSpecialCommitMergeFlags = CheckBitSet(SE_SACL_PROTECTED, tControl) ? - PROTECTED_SACL_SECURITY_INFORMATION : UNPROTECTED_SACL_SECURITY_INFORMATION; iSpecialCommitMergeFlags = CheckBitSet(SE_DACL_PROTECTED, tControl) ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION; - + // mark all elements as needing to be updated bDaclIsDirty = true; bSaclIsDirty = true; @@ -204,11 +202,11 @@ void AnalyzeSecurity(ObjectEntry & oEntry) // clear out any remaining data InputOutput::WriteToScreen(); - iFilesUpdatedFailure++; + ++iFilesUpdatedFailure; } else { - iFilesUpdatedSuccess++; + ++iFilesUpdatedSuccess; } } } @@ -238,7 +236,7 @@ void CompleteEntry(ObjectEntry & oEntry, bool bDecreaseCounter = true) InputOutput::WriteToScreen(); } -void AnalzyingQueue() +void AnalyzingQueue() { // total files processed thread_local BYTE DirectoryInfo[MAX_DIRECTORY_BUFFER]; @@ -249,7 +247,7 @@ void AnalzyingQueue() ObjectEntry oEntry = oScanQueue.pop(); // break out if entry flags a termination - if (oEntry.Name.size() == 0) break; + if (oEntry.Name.empty()) break; // skip if hidden and system if (!oEntry.IsRoot && IsHiddenSystem(oEntry.Attributes) @@ -290,7 +288,7 @@ void AnalzyingQueue() { InputOutput::AddError(L"Access denied error occurred while enumerating directory"); CompleteEntry(oEntry); - iFilesEnumerationFailures++; + ++iFilesEnumerationFailures; continue; } else if (Status == STATUS_OBJECT_PATH_NOT_FOUND || @@ -298,23 +296,23 @@ void AnalzyingQueue() { InputOutput::AddError(L"Path not found error occurred while enumerating directory"); CompleteEntry(oEntry); - iFilesEnumerationFailures++; + ++iFilesEnumerationFailures; continue; } else if (Status != STATUS_SUCCESS) { InputOutput::AddError(L"Unknown error occurred while enumerating directory"); CompleteEntry(oEntry); - iFilesEnumerationFailures++; + ++iFilesEnumerationFailures; continue; } // enumerate files in the directory - for (;;) + for (bool bFirstRun = true; true; bFirstRun = false) { Status = NtQueryDirectoryFile(hFindFile, NULL, NULL, NULL, &IoStatusBlock, DirectoryInfo, MAX_DIRECTORY_BUFFER, (FILE_INFORMATION_CLASS)FileDirectoryInformation, - FALSE, NULL, FALSE); + FALSE, NULL, (bFirstRun) ? TRUE : FALSE); // done processing if (Status == STATUS_NO_MORE_FILES) break; @@ -325,7 +323,7 @@ void AnalzyingQueue() break; } - for (FILE_DIRECTORY_INFORMATION * oInfo = (FILE_DIRECTORY_INFORMATION *)DirectoryInfo; + for (auto* oInfo = (FILE_DIRECTORY_INFORMATION *)DirectoryInfo; oInfo != NULL; oInfo = (FILE_DIRECTORY_INFORMATION *)((BYTE *)oInfo + oInfo->NextEntryOffset)) { // continue immediately if we get the '.' or '..' entries @@ -354,7 +352,7 @@ void AnalzyingQueue() } else { - iFilesToProcess++; + ++iFilesToProcess; oScanQueue.push(oSubEntry); } @@ -374,10 +372,9 @@ VOID BeginFileScan() // startup some threads for processing std::vector oThreads; for (USHORT iNum = 0; iNum < InputOutput::MaxThreads(); iNum++) - oThreads.push_back(new std::thread(AnalzyingQueue)); + oThreads.push_back(new std::thread(AnalyzingQueue)); - for (std::vector::iterator sScanPath = InputOutput::ScanPaths().begin(); - sScanPath != InputOutput::ScanPaths().end(); sScanPath++) + for (auto sPath : InputOutput::ScanPaths()) { // to get the process started, we need to have one entry so // we will set that to the passed argument @@ -385,11 +382,9 @@ VOID BeginFileScan() oEntryFirst.IsRoot = true; // make a local copy of the path since we may have to alter it - std::wstring sPath = *sScanPath; - // handle special case where a drive root is specified // we must ensure it takes the form x:\. to resolve correctly - size_t iSemiColon = sPath.rfind(L":"); + size_t iSemiColon = sPath.rfind(L':'); if (iSemiColon != std::wstring::npos) { std::wstring sEnd = sPath.substr(iSemiColon); @@ -411,7 +406,7 @@ VOID BeginFileScan() RtlFreeUnicodeString(&tPathU); // add this entry to being processing - iFilesToProcess++; + ++iFilesToProcess; oScanQueue.push(oEntryFirst); } @@ -425,21 +420,24 @@ VOID BeginFileScan() // send in some empty entries to tell the thread to stop waiting for (USHORT iNum = 0; iNum < InputOutput::MaxThreads(); iNum++) { - ObjectEntry oEntry = { L"" }; + ObjectEntry oEntry = { L"", 0, FALSE }; oScanQueue.push(oEntry); } // wait for the threads to complete - for (std::vector::iterator oThread = oThreads.begin(); - oThread != oThreads.end(); oThread++) + for (auto& oThread : oThreads) { - (*oThread)->join(); - delete (*oThread); + oThread->join(); + delete oThread; } } int wmain(int iArgs, WCHAR * aArgs[]) { + // allow output of unicode characters + _setmode(_fileno(stderr), _O_U16TEXT); + _setmode(_fileno(stdout), _O_U16TEXT); + // print standard header wprintf(L"===============================================================================\n"); wprintf(L"= Repacls Version %hs by Bryan Berns\n", VERSION_STRING); @@ -497,7 +495,7 @@ int wmain(int iArgs, WCHAR * aArgs[]) } // verify a path was specified - if (InputOutput::ScanPaths().size() == 0) + if (InputOutput::ScanPaths().empty()) { wprintf(L"%s\n", L"ERROR: No path was specified."); exit(-1); @@ -511,9 +509,8 @@ int wmain(int iArgs, WCHAR * aArgs[]) wprintf(L"===============================================================================\n"); wprintf(L"= Initial Scan Details\n"); wprintf(L"===============================================================================\n"); - for (std::vector::iterator sScanPath = InputOutput::ScanPaths().begin(); - sScanPath != InputOutput::ScanPaths().end(); sScanPath++) - wprintf(L"= Scan Path(s): %s\n", (*sScanPath).c_str()); + for (auto& sScanPath : InputOutput::ScanPaths()) + wprintf(L"= Scan Path(s): %s\n", sScanPath.c_str()); wprintf(L"= Maximum Threads: %d\n", (int)InputOutput::MaxThreads()); wprintf(L"= What If Mode: %s\n", InputOutput::InWhatIfMode() ? L"Yes" : L"No"); wprintf(L"= Antivirus Active: %s\n", GetAntivirusStateDescription().c_str()); diff --git a/Operation.cpp b/Operation.cpp index 1508506..11594d6 100644 --- a/Operation.cpp +++ b/Operation.cpp @@ -27,7 +27,7 @@ bool Operation::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEnt PSID const tCurrentSid = &tAce->Sid; PSID tResultantSid; - SidActionResult tResult = DetermineSid(sSdPart, tObjectEntry, tCurrentSid, tResultantSid); + const SidActionResult tResult = DetermineSid(sSdPart, tObjectEntry, tCurrentSid, tResultantSid); if (tResult == SidActionResult::Remove) { @@ -75,6 +75,12 @@ bool Operation::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEnt else { PBYTE const tNewAcl = (PBYTE)LocalAlloc(LMEM_FIXED, tAcl->AclSize + (iNewLen - iOldLen)); + if (tNewAcl == NULL) + { + wprintf(L"ERROR: Unable to allocate memory for new SID.\n"); + exit(-1); + } + memcpy(tNewAcl, tAclLoc, tOldSidLoc - tAclLoc); memcpy(tNewAcl + (tOldSidLoc - tAclLoc), tNewSid, iNewLen); memcpy(tNewAcl + (tOldSidLoc - tAclLoc) + iNewLen, tOldSidLoc + iOldLen, tAcl->AclSize - ((tOldSidLoc - tAclLoc) + iOldLen)); @@ -98,10 +104,10 @@ bool Operation::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & tObjectEnt return bMadeChange; } -std::vector Operation::SplitArgs(std::wstring sInput, std::wstring sDelimiter) +std::vector Operation::SplitArgs(std::wstring sInput, const std::wstring & sDelimiter) { - std::wregex oRegex(sDelimiter); - std::wsregex_token_iterator oFirst{ sInput.begin(), sInput.end(), oRegex, -1 }, oLast; + const std::wregex oRegex(sDelimiter); + const std::wsregex_token_iterator oFirst{ sInput.begin(), sInput.end(), oRegex, -1 }, oLast; return { oFirst, oLast }; } @@ -139,10 +145,10 @@ Operation::Operation(std::queue & oArgList) DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &DefaultSidWhenEmpty); }; -std::vector Operation::ProcessAndCheckArgs(int iArgsRequired, std::queue & oArgList, std::wstring sDelimiter) +std::vector Operation::ProcessAndCheckArgs(int iArgsRequired, std::queue & oArgList, const std::wstring & sDelimiter) { // check if around arguments exist yet - if (iArgsRequired > 0 && oArgList.size() == 0) + if (iArgsRequired > 0 && oArgList.empty()) { wprintf(L"ERROR: An option that was specified is missing a required parameter.\n"); exit(-1); @@ -166,8 +172,8 @@ std::vector Operation::ProcessAndCheckArgs(int iArgsRequired, std: void Operation::ProcessGranularTargetting(std::wstring sScope) { // parse the parameters, splitting on : - std::wregex oRegex(L","); - std::wsregex_token_iterator oFirst{ sScope.begin(), sScope.end(), oRegex, -1 }, oLast; + const std::wregex oRegex(L","); + const std::wsregex_token_iterator oFirst{ sScope.begin(), sScope.end(), oRegex, -1 }, oLast; std::vector sScopeOpts = { oFirst, oLast }; // default all to false if calling this method @@ -177,17 +183,16 @@ void Operation::ProcessGranularTargetting(std::wstring sScope) AppliesToGroup = false; AppliesToObject = false; - for (std::vector::iterator oScope = sScopeOpts.begin(); - oScope != sScopeOpts.end(); oScope++) + for (auto& sScopeOpt : sScopeOpts) { - if (*oScope == L"DACL") AppliesToDacl = true; - else if (*oScope == L"SACL") AppliesToSacl = true; - else if (*oScope == L"OWNER") AppliesToOwner = true; - else if (*oScope == L"GROUP") AppliesToGroup = true; + if (sScopeOpt == L"DACL") AppliesToDacl = true; + else if (sScopeOpt == L"SACL") AppliesToSacl = true; + else if (sScopeOpt == L"OWNER") AppliesToOwner = true; + else if (sScopeOpt == L"GROUP") AppliesToGroup = true; else { // complain - wprintf(L"ERROR: Unrecognized scope qualifier '%s'\n", (*oScope).c_str()); + wprintf(L"ERROR: Unrecognized scope qualifier '%s'\n", sScopeOpt.c_str()); exit(-1); } } diff --git a/Operation.h b/Operation.h index 4344e4d..ad243b1 100644 --- a/Operation.h +++ b/Operation.h @@ -24,7 +24,7 @@ typedef ACCESS_ACE *PACCESS_ACE; #define NextAceWithRestart(Acl,Ace,Restart) ((Restart) ? FirstAce(Acl) : NextAce(Ace)) // define our own version of sid length since its faster -#define GetLengthSid(x) (sizeof(SID) + (((SID *) x)->SubAuthorityCount - 1) * sizeof(((SID *) x)->SubAuthority)) +#define GetLengthSid(x) (sizeof(SID) + (((SID *) (x))->SubAuthorityCount - 1) * sizeof(((SID *) (x))->SubAuthority)) #define SidMatch(x,y) (memcmp(x,y,min(GetLengthSid(x),GetLengthSid(y))) == 0) #define SidNotMatch(x,y) (!SidMatch(x,y)) @@ -35,12 +35,12 @@ typedef ACCESS_ACE *PACCESS_ACE; #define IsReparsePoint(x) (CheckBitSet(x,FILE_ATTRIBUTE_REPARSE_POINT)) // a few simple defines for convenience -#define IsInherited(x) CheckBitSet(x->Header.AceFlags,INHERITED_ACE) -#define HasContainerInherit(x) CheckBitSet(x->Header.AceFlags,CONTAINER_INHERIT_ACE) -#define HasObjectInherit(x) CheckBitSet(x->Header.AceFlags,OBJECT_INHERIT_ACE) -#define HasInheritOnly(x) CheckBitSet(x->Header.AceFlags,INHERIT_ONLY_ACE) -#define HasNoPropogate(x) CheckBitSet(x->Header.AceFlags,NO_PROPAGATE_INHERIT_ACE) -#define GetNonOiCiIoBits(x) ((~(CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE | INHERIT_ONLY_ACE)) & x->Header.AceFlags) +#define IsInherited(x) CheckBitSet((x)->Header.AceFlags,INHERITED_ACE) +#define HasContainerInherit(x) CheckBitSet((x)->Header.AceFlags,CONTAINER_INHERIT_ACE) +#define HasObjectInherit(x) CheckBitSet((x)->Header.AceFlags,OBJECT_INHERIT_ACE) +#define HasInheritOnly(x) CheckBitSet((x)->Header.AceFlags,INHERIT_ONLY_ACE) +#define HasNoPropogate(x) CheckBitSet((x)->Header.AceFlags,NO_PROPAGATE_INHERIT_ACE) +#define GetNonOiCiIoBits(x) ((~(CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE | INHERIT_ONLY_ACE)) & (x)->Header.AceFlags) typedef struct ObjectEntry { @@ -63,8 +63,8 @@ class Operation { protected: - static std::vector Operation::SplitArgs(std::wstring sInput, std::wstring sDelimiter); - static std::vector ProcessAndCheckArgs(int iArgsRequired, std::queue & oArgList, std::wstring sDelimiter = L":"); + static std::vector SplitArgs(std::wstring sInput, const std::wstring & sDelimiter); + static std::vector ProcessAndCheckArgs(int iArgsRequired, std::queue & oArgList, const std::wstring & sDelimiter = L":"); void ProcessGranularTargetting(std::wstring sScope); public: @@ -90,6 +90,7 @@ class Operation virtual void ProcessObjectAction(ObjectEntry & tObjectEntry) { return; } Operation(std::queue & oArgList); + virtual ~Operation() = default;; }; #include "OperationFactory.h" \ No newline at end of file diff --git a/OperationCopyDomain.cpp b/OperationCopyDomain.cpp index 81cf202..0b018da 100644 --- a/OperationCopyDomain.cpp +++ b/OperationCopyDomain.cpp @@ -52,6 +52,9 @@ bool OperationCopyDomain::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & for (LONG iEntry = 0; iEntry < tCurrentAcl->AceCount; tAceDacl = (iEntry == -1) ? FirstAce(tCurrentAcl) : NextAce(tAceDacl), iEntry++) { + // do not bother with inherited aces + if (IsInherited(tAceDacl)) continue; + // see if this sid in the source domain BOOL bDomainSidsEqual = FALSE; if (EqualDomainSid(&tAceDacl->Sid, tSourceDomain, &bDomainSidsEqual) == 0 || @@ -78,7 +81,7 @@ bool OperationCopyDomain::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & // lookup the target name and see if it exists std::wstring sTargetAccountName = GetNameFromSid(tSidTmp); FreeSid(tSidTmp); - if (sTargetAccountName.size() == 0) continue; + if (sTargetAccountName.empty()) continue; // do a forward lookup on the name in order to get a reference to the // SID that we do not have to worry about cleaning up @@ -94,14 +97,19 @@ bool OperationCopyDomain::ProcessAclAction(WCHAR * const sSdPart, ObjectEntry & { // translate the old sid to an account name std::wstring sSourceAccountName = GetNameFromSid(&tAceDacl->Sid, NULL); - if (sSourceAccountName.size() == 0) continue; + if (sSourceAccountName.empty()) continue; // check to see if an equivalent account exists in the target domain std::wstring sTargetAccountName = sTargetDomain + (wcsstr(sSourceAccountName.c_str(), L"\\") + 1); tTargetAccountSid = GetSidFromName(sTargetAccountName); // continue if no match was found - if (tTargetAccountSid == nullptr) continue; + if (tTargetAccountSid == nullptr) + { + InputOutput::AddWarning(L"Could not find matching account in target domain for '" + + sSourceAccountName + L"'"); + continue; + } // do a reverse lookup to see if this might be a sid history item if (GetNameFromSidEx(tTargetAccountSid) == sSourceAccountName) continue; diff --git a/OperationDomainPaths.cpp b/OperationDomainPaths.cpp index 06d15bb..5e9d311 100644 --- a/OperationDomainPaths.cpp +++ b/OperationDomainPaths.cpp @@ -23,7 +23,12 @@ OperationDomainPaths::OperationDomainPaths(std::queue & oArgList) std::vector sSubArgs = ProcessAndCheckArgs(1, oArgList); // initialize com only - static HRESULT hComInit = CoInitializeEx(NULL, 0); + static HRESULT hComInit = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (hComInit != S_OK && hComInit != S_FALSE) + { + wprintf(L"ERROR: Could not initialize COM.\n"); + exit(-1); + } // find a domain controller for the specified domain PDOMAIN_CONTROLLER_INFO tDomainControllerInfo; @@ -72,7 +77,7 @@ OperationDomainPaths::OperationDomainPaths(std::queue & oArgList) // execute the search. LPWSTR sAttributes[] = { L"cn" }; ADS_SEARCH_HANDLE hSearch; - if (FAILED(oSearch->ExecuteSearch(sSearchFilter, sAttributes, ARRAYSIZE(sAttributes), &hSearch))) + if (FAILED(oSearch->ExecuteSearch(sSearchFilter, sAttributes, _countof(sAttributes), &hSearch))) { wprintf(L"ERROR: Could not execute search for domain '%s'\n", sSubArgs[0].c_str()); exit(-1); diff --git a/OperationFactory.h b/OperationFactory.h index 7e2dd17..b0d3417 100644 --- a/OperationFactory.h +++ b/OperationFactory.h @@ -22,7 +22,7 @@ class FactoryPlant std::wstring sCommand = oArgList.front(); oArgList.pop(); // error if the string is least one character long - if (sCommand.size() == 0) return nullptr; + if (sCommand.empty()) return nullptr; // error if the string does not start with "/" or "-" if (sCommand.at(0) != '/' && sCommand.at(0) != '-') @@ -39,8 +39,7 @@ class FactoryPlant sCommand.erase(0, 1); // see if there's a class that matches this - std::map::iterator - oCommand = GetCommands().find(sCommand); + const auto oCommand = GetCommands().find(sCommand); // error if there is no matching command if (oCommand == GetCommands().end()) @@ -58,7 +57,7 @@ template class ClassFactory : public FactoryPlant { private: - Operation * CreateInstanceSub(std::queue & oArgList) + Operation * CreateInstanceSub(std::queue & oArgList) override { return new SubType(oArgList); } diff --git a/OperationFindDomain.cpp b/OperationFindDomain.cpp index 739af90..06ffb4a 100644 --- a/OperationFindDomain.cpp +++ b/OperationFindDomain.cpp @@ -23,7 +23,7 @@ OperationFindDomain::OperationFindDomain(std::queue & oArgList) : // do a reverse lookup of the name for reporting sDomainName = GetDomainNameFromSid(tDomainSid); - sDomainName = sDomainName.substr(0, sDomainName.find(L"\\")); + sDomainName = sDomainName.substr(0, sDomainName.find(L'\\')); // flag this as being an ace-level action AppliesToDacl = true; diff --git a/OperationHelp.cpp b/OperationHelp.cpp index 07054c6..b81a912 100644 --- a/OperationHelp.cpp +++ b/OperationHelp.cpp @@ -76,12 +76,16 @@ or end of your command as to not confuse them with ordered parameters. /Threads Specifies the number of threads to use while processing. The default value - of '5' is usually adequate, but can be increased if performing changes + of '5' is usually adequate, but can be increased if performing changes over a higher-latency connection. Since changes to a parent directory often affect the inherited security on children, the security of children objects are always processed after the security on their parent objects are fully processed. +/Log + Specifies that messages written to the screen should also be written to the + designated file. The file is a comma separated value file. + /WhatIf This option will analyze security and report on any potential changes without actually committing the data. Use of /WhatIf is recommended for @@ -159,7 +163,10 @@ Commands That Can Alter Security (When /WhatIf Is Not Present) granted. This command is useful to correct issues where a user or administrator has mistakenly removed an administrative group from some directories. +)"; + std::wcout << + LR"( /Compact This command will look for mergeable entries in the security descriptor and merge them. For example, running icacls.exe /grant Everyone:R @@ -170,10 +177,7 @@ Commands That Can Alter Security (When /WhatIf Is Not Present) inefficiencies. While there's nothing inherently wrong with these entries, it possible for them to result file system is performance degradation. -)"; - std::wcout << - LR"( /CopyDomain : This command is identical to /MoveDomain except that the original entry referring the SourceDomainName is retained instead of replaced. diff --git a/OperationLocate.cpp b/OperationLocate.cpp index 59fa8fc..3776b65 100644 --- a/OperationLocate.cpp +++ b/OperationLocate.cpp @@ -5,7 +5,7 @@ ClassFactory * OperationLocate::RegisteredFactory = new ClassFactory(GetCommand()); -#define Q(x) L"\"" + x + L"\"" +#define Q(x) L"\"" + (x) + L"\"" OperationLocate::OperationLocate(std::queue & oArgList) : Operation(oArgList) { @@ -32,7 +32,7 @@ OperationLocate::OperationLocate(std::queue & oArgList) : Operatio if (hFile == hReportFile) { // write out the file type marker - BYTE hHeader[] = { 0xEF,0xBB,0xBF }; + const BYTE hHeader[] = { 0xEF,0xBB,0xBF }; DWORD iBytes = 0; if (WriteFile(hFile, &hHeader, _countof(hHeader), &iBytes, NULL) == 0) { @@ -69,7 +69,7 @@ void OperationLocate::ProcessObjectAction(ObjectEntry & tObjectEntry) { // skip any file names that do not match the regex const WCHAR * sFileName = tObjectEntry.Name.c_str(); - if (wcschr(sFileName, '\\') != NULL) sFileName = wcschr(sFileName, '\\'); + if (wcsrchr(sFileName, '\\') != NULL) sFileName = wcsrchr(sFileName, '\\') + 1; if (!std::regex_match(sFileName, tRegex)) return; // fetch file attribute data @@ -80,7 +80,7 @@ void OperationLocate::ProcessObjectAction(ObjectEntry & tObjectEntry) } // convert the file size to a string - WCHAR sSize[32]; + WCHAR sSize[32] = { 0 }; ULARGE_INTEGER iFileSize; iFileSize.LowPart = tData.nFileSizeLow; iFileSize.HighPart = tData.nFileSizeHigh; diff --git a/OperationLocateShortcut.cpp b/OperationLocateShortcut.cpp new file mode 100644 index 0000000..10bd265 --- /dev/null +++ b/OperationLocateShortcut.cpp @@ -0,0 +1,160 @@ +#include "OperationLocateShortcut.h" +#include "InputOutput.h" +#include "Functions.h" + +#pragma comment(lib, "shlwapi.lib") + +#include +#include + +ClassFactory * OperationLocateShortcut::RegisteredFactory = +new ClassFactory(GetCommand()); + +#define Q(x) L"\"" + (x) + L"\"" + +OperationLocateShortcut::OperationLocateShortcut(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to parse + std::vector sReportFile = ProcessAndCheckArgs(1, oArgList, L"\\0"); + std::vector sMatchAndArgs = ProcessAndCheckArgs(1, oArgList, L"\\0"); + + // fetch params + HANDLE hFile = CreateFile(sReportFile[0].c_str(), GENERIC_WRITE, + FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + // see if names could be resolved + if (hFile == INVALID_HANDLE_VALUE) + { + // complain + wprintf(L"ERROR: Could not create file '%s' specified for parameter '%s'.\n", sReportFile[0].c_str(), GetCommand().c_str()); + exit(-1); + } + + // register the file handle + hReportFile = RegisterFileHandle(hFile, GetCommand()); + + // if this is the first handle using this file, write out a header + if (hFile == hReportFile) + { + // write out the file type marker + const BYTE hHeader[] = { 0xEF,0xBB,0xBF }; + DWORD iBytes = 0; + if (WriteFile(hFile, &hHeader, _countof(hHeader), &iBytes, NULL) == 0) + { + wprintf(L"ERROR: Could not write out file type marker '%s'.\n", GetCommand().c_str()); + exit(-1); + } + + // write out the header + std::wstring sToWrite = std::wstring(L"") + Q(L"Path") + L"," + Q(L"Creation Time") + L"," + + Q(L"Modified Time") + L"," + Q(L"Size") + L"," + Q(L"Attributes") + L"," + + Q(L"Target Path") + L"," + Q(L"Working Directory") + L"\r\n"; + if (WriteToFile(sToWrite, hReportFile) == 0) + { + wprintf(L"ERROR: Could not write header to report file for parameter '%s'.\n", GetCommand().c_str()); + exit(-1); + } + } + + // only flag this to apply to the core object with the file name + AppliesToObject = true; + + // compile the regular expression + try + { + tRegexTarget = std::wregex(sMatchAndArgs[0], std::wregex::icase | std::wregex::optimize); + tRegexLink = std::wregex(L".*\\.lnk", std::wregex::icase | std::wregex::optimize); + } + catch (const std::regex_error &) + { + wprintf(L"ERROR: Invalid regular expression '%s' specified for parameter '%s'.\n", sMatchAndArgs[0].c_str(), GetCommand().c_str()); + exit(-1); + } +} + +void OperationLocateShortcut::ProcessObjectAction(ObjectEntry & tObjectEntry) +{ + // skip any file names that do not match the regex + const WCHAR * sFileName = tObjectEntry.Name.c_str(); + if (wcsrchr(sFileName, '\\') != NULL) sFileName = wcsrchr(sFileName, '\\') + 1; + if (!std::regex_match(sFileName, tRegexLink)) return; + + // initialize com for this thread + __declspec(thread) static bool bComInitialized = false; + if (!bComInitialized) + { + bComInitialized = true; + const HRESULT hComInit = CoInitializeEx(0, COINIT_APARTMENTTHREADED); + if (hComInit != S_OK && hComInit != S_FALSE) + { + wprintf(L"ERROR: Could not initialize COM.\n"); + exit(-1); + } + } + + // fetch file attribute data + WIN32_FILE_ATTRIBUTE_DATA tData; + if (GetFileAttributesExW(tObjectEntry.Name.c_str(), GetFileExInfoStandard, &tData) == 0) + { + InputOutput::AddError(L"ERROR: Unable to read file attributes."); + } + + // convert the file size to a string + WCHAR sSize[32] = { 0 }; + ULARGE_INTEGER iFileSize; + iFileSize.LowPart = tData.nFileSizeLow; + iFileSize.HighPart = tData.nFileSizeHigh; + setlocale(LC_NUMERIC, ""); + wsprintf(sSize, L"%I64u", iFileSize.QuadPart); + + // decode attributes + std::wstring sAttributes = L""; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) sAttributes += L"R"; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) sAttributes += L"H"; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) sAttributes += L"S"; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) sAttributes += L"D"; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE) sAttributes += L"A"; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) sAttributes += L"T"; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) sAttributes += L"C"; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) sAttributes += L"O"; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) sAttributes += L"N"; + if (tData.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) sAttributes += L"E"; + + // create shortcut interfaces + IShellLinkW * oLink = NULL; + IPersistFile * oFile = NULL; + if (CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (VOID **)&oLink) != S_OK || + oLink->QueryInterface(IID_IPersistFile, (VOID **)&oFile) != S_OK) + { + wprintf(L"ERROR: Could not initialize ShellLink COM instance.\n"); + return; + } + + // load in the shortcut + std::wstring sTargetPath = L""; + std::wstring sWorkingDirectory = L""; + if (oFile->Load(tObjectEntry.Name.c_str(), STGM_READ) == S_OK) + { + WCHAR sTargetPathRaw[MAX_PATH]; + WCHAR sWorkingDirRaw[MAX_PATH]; + if (oLink->GetPath(sTargetPathRaw, MAX_PATH, NULL, SLGP_RAWPATH) == S_OK) sTargetPath = sTargetPathRaw; + if (oLink->GetWorkingDirectory(sWorkingDirRaw, MAX_PATH) == S_OK) sWorkingDirectory = sWorkingDirRaw; + } + + // check if the target path matches out regex filter + if (std::regex_match(sTargetPath, tRegexTarget)) + { + // write output to file + std::wstring sToWrite = std::wstring(L"") + Q(tObjectEntry.Name) + L"," + + Q(FileTimeToString(&tData.ftCreationTime)) + L"," + Q(FileTimeToString(&tData.ftLastWriteTime)) + + L"," + Q(sSize) + L"," + Q(sAttributes) + L"," + Q(sTargetPath) + L"," + Q(sWorkingDirectory) + L"\r\n"; + if (WriteToFile(sToWrite, hReportFile) == 0) + { + InputOutput::AddError(L"ERROR: Unable to write security information to report file."); + } + } + + // cleanup + oLink->Release(); + oFile->Release(); +} \ No newline at end of file diff --git a/OperationLocateShortcut.h b/OperationLocateShortcut.h new file mode 100644 index 0000000..e0fd3f1 --- /dev/null +++ b/OperationLocateShortcut.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "Operation.h" + +class OperationLocateShortcut : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"LocateShortcut"; } + static ClassFactory * RegisteredFactory; + + // operation specific + HANDLE hReportFile = INVALID_HANDLE_VALUE; + std::wregex tRegexTarget; + std::wregex tRegexLink; + +public: + + // overrides + void ProcessObjectAction(ObjectEntry & tObjectEntry) override; + + // constructors + OperationLocateShortcut(std::queue & oArgList); +}; \ No newline at end of file diff --git a/OperationLog.cpp b/OperationLog.cpp new file mode 100644 index 0000000..18501a9 --- /dev/null +++ b/OperationLog.cpp @@ -0,0 +1,73 @@ +#include "OperationLog.h" +#include "InputOutput.h" +#include "Functions.h" + +#include +#include +#include +#include + +ClassFactory * OperationLog::RegisteredFactory = +new ClassFactory(GetCommand()); + +#define Q(x) L"\"" + (x) + L"\"" + +HANDLE OperationLog::hLogHandle = INVALID_HANDLE_VALUE; + +OperationLog::OperationLog(std::queue & oArgList) : Operation(oArgList) +{ + // exit if there are not enough arguments to parse + std::vector sLogFile = ProcessAndCheckArgs(1, oArgList, L"\\0"); + + // exit immediately if command had already been called + if (hLogHandle != INVALID_HANDLE_VALUE) + { + wprintf(L"ERROR: %s cannot be specified more than once.", GetCommand().c_str()); + exit(-1); + } + + // fetch params + hLogHandle = CreateFile(sLogFile[0].c_str(), GENERIC_WRITE, + FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + // write out the file type marker + const BYTE hHeader[] = { 0xEF,0xBB,0xBF }; + DWORD iBytes = 0; + if (WriteFile(hLogHandle, &hHeader, _countof(hHeader), &iBytes, NULL) == 0) + { + wprintf(L"ERROR: Could not write out file type marker '%s'.\n", GetCommand().c_str()); + exit(-1); + } + + // write out the header + std::wstring sToWrite = std::wstring(L"") + Q(L"Time") + L"," + Q(L"Type") + L"," + Q(L"Path") + L"," + Q(L"Message") + L"\r\n"; + if (WriteToFile(sToWrite, hLogHandle) == 0) + { + wprintf(L"ERROR: Could not write header to log file for parameter '%s'.\n", GetCommand().c_str()); + exit(-1); + } + + // enable input/output routines to try to log data using this class + InputOutput::Log() = true; +} + +void OperationLog::LogFileItem(const std::wstring & sInfoLevel, const std::wstring & sPath, const std::wstring & sMessage) +{ + // sanity check + if (hLogHandle == INVALID_HANDLE_VALUE) return; + + // get time string + WCHAR sDate[20]; + const __time64_t tUtcTime = _time64(NULL); + struct tm tLocalTime; + _localtime64_s(&tLocalTime, &tUtcTime); + wcsftime(sDate, _countof(sDate), L"%Y-%m-%d %H:%M:%S", &tLocalTime); + + // write out information + std::wstring sToWrite = std::wstring(L"") + Q(sDate) + L"," + Q(sInfoLevel) + L"," + Q(sPath) + L"," + Q(sMessage) + L"\r\n"; + if (WriteToFile(sToWrite, hLogHandle) == 0) + { + wprintf(L"ERROR: Could not write data to log file for parameter '%s'.\n", GetCommand().c_str()); + exit(-1); + } +} \ No newline at end of file diff --git a/OperationLog.h b/OperationLog.h new file mode 100644 index 0000000..a95dabe --- /dev/null +++ b/OperationLog.h @@ -0,0 +1,22 @@ +#pragma once + +#include "Operation.h" + +class OperationLog : public Operation +{ +private: + + // statics used by command registration utility + static std::wstring GetCommand() { return L"Log"; } + static ClassFactory * RegisteredFactory; + + static HANDLE hLogHandle; + +public: + + // constructors + OperationLog(std::queue & oArgList); + + // functions + static void LogFileItem(const std::wstring & sInfoLevel, const std::wstring & sPath, const std::wstring & sMessage); +}; diff --git a/OperationMoveDomain.cpp b/OperationMoveDomain.cpp index b414978..1a8a0cd 100644 --- a/OperationMoveDomain.cpp +++ b/OperationMoveDomain.cpp @@ -71,7 +71,7 @@ SidActionResult OperationMoveDomain::DetermineSid(WCHAR * const sSdPart, ObjectE // lookup the target name and see if it exists std::wstring sTargetAccountName = GetNameFromSid(tSidTmp); FreeSid(tSidTmp); - if (sTargetAccountName.size() == 0) return SidActionResult::Nothing; + if (sTargetAccountName.empty()) return SidActionResult::Nothing; // do a forward lookup on the name in order to get a reference to the // SID that we do not have to worry about cleaning up @@ -87,14 +87,19 @@ SidActionResult OperationMoveDomain::DetermineSid(WCHAR * const sSdPart, ObjectE { // translate the old sid to an account name std::wstring sSourceAccountName = GetNameFromSid(tCurrentSid, NULL); - if (sSourceAccountName.size() == 0) return SidActionResult::Nothing; + if (sSourceAccountName.empty()) return SidActionResult::Nothing; // check to see if an equivalent account exists in the target domain std::wstring sTargetAccountName = sTargetDomain + (wcsstr(sSourceAccountName.c_str(), L"\\") + 1); tResultantSid = GetSidFromName(sTargetAccountName); // exit if no match was found - if (tResultantSid == nullptr) return SidActionResult::Nothing; + if (tResultantSid == nullptr) + { + InputOutput::AddWarning(L"Could not find matching account in target domain for '" + + sSourceAccountName + L"'"); + return SidActionResult::Nothing; + } // do a reverse lookup to see if this might be a sid history item if (GetNameFromSidEx(tResultantSid) == sSourceAccountName) return SidActionResult::Nothing; diff --git a/OperationPathList.cpp b/OperationPathList.cpp index 3fe3c9a..d123629 100644 --- a/OperationPathList.cpp +++ b/OperationPathList.cpp @@ -19,7 +19,7 @@ OperationPathList::OperationPathList(std::queue & oArgList) : Oper std::wifstream fFile(sSubArgs[0].c_str()); // adapt the stream to read windows unicode files - fFile.imbue(std::locale(fFile.getloc(), new std::codecvt_utf8)); // read the file line-by-line diff --git a/OperationReplaceMap.cpp b/OperationReplaceMap.cpp index d4a39ed..3d45388 100644 --- a/OperationReplaceMap.cpp +++ b/OperationReplaceMap.cpp @@ -19,7 +19,7 @@ OperationReplaceMap::OperationReplaceMap(std::queue & oArgList) : std::wifstream fFile(sSubArgs[0].c_str()); // adapt the stream to read windows unicode files - fFile.imbue(std::locale(fFile.getloc(), new std::codecvt_utf8)); // read the file line-by-line @@ -71,7 +71,7 @@ OperationReplaceMap::OperationReplaceMap(std::queue & oArgList) : SidActionResult OperationReplaceMap::DetermineSid(WCHAR * const sSdPart, ObjectEntry & tObjectEntry, PSID const tCurrentSid, PSID & tResultantSid) { // check if the sid matches the ace - std::map::iterator oInteractor = oReplaceMap.find(tCurrentSid); + const auto oInteractor = oReplaceMap.find(tCurrentSid); if (oInteractor == oReplaceMap.end()) return SidActionResult::Nothing; // return the replacement sid diff --git a/OperationReport.cpp b/OperationReport.cpp index e5d898f..dd81e8d 100644 --- a/OperationReport.cpp +++ b/OperationReport.cpp @@ -5,7 +5,7 @@ ClassFactory * OperationReport::RegisteredFactory = new ClassFactory(GetCommand()); -#define Q(x) L"\"" + x + L"\"" +#define Q(x) L"\"" + (x) + L"\"" OperationReport::OperationReport(std::queue & oArgList) : Operation(oArgList) { diff --git a/OperationResetChildren.h b/OperationResetChildren.h index 107a538..246791e 100644 --- a/OperationResetChildren.h +++ b/OperationResetChildren.h @@ -11,7 +11,7 @@ class OperationResetChildren : public Operation static ClassFactory * RegisteredFactory; // used for clearing out explicit aces - ACL tAclNull = { 0 }; + ACL tAclNull = { 0, 0, 0, 0, 0 }; public: diff --git a/OperationRestoreSecurity.cpp b/OperationRestoreSecurity.cpp index ba5efbe..2367e76 100644 --- a/OperationRestoreSecurity.cpp +++ b/OperationRestoreSecurity.cpp @@ -19,7 +19,7 @@ OperationRestoreSecurity::OperationRestoreSecurity(std::queue & oA std::wifstream fFile(sSubArgs[0].c_str()); // adapt the stream to read windows unicode files - fFile.imbue(std::locale(fFile.getloc(), new std::codecvt_utf8)); // read the file line-by-line @@ -58,7 +58,7 @@ OperationRestoreSecurity::OperationRestoreSecurity(std::queue & oA bool OperationRestoreSecurity::ProcessSdAction(std::wstring & sFileName, ObjectEntry & tObjectEntry, PSECURITY_DESCRIPTOR & tDescriptor, bool & bDescReplacement) { - std::map::iterator oSecInfo = oImportMap.find(sFileName); + auto oSecInfo = oImportMap.find(sFileName); if (oSecInfo != oImportMap.end()) { // lookup the string in the map diff --git a/OperationSharePaths.cpp b/OperationSharePaths.cpp index b0caa2a..4898df4 100644 --- a/OperationSharePaths.cpp +++ b/OperationSharePaths.cpp @@ -29,17 +29,16 @@ OperationSharePaths::OperationSharePaths(std::queue & oArgList) : std::vector oShareArgs = SplitArgs(sSubArgs[1], L","); // enumerate list - for (std::vector::iterator sShareArg = oShareArgs.begin(); - sShareArg != oShareArgs.end(); sShareArg++) + for (auto& oShareArg : oShareArgs) { // check to see if a match parameter was passed WCHAR sMatchArg[] = L"MATCH="; WCHAR sNoMatchArg[] = L"NOMATCH="; - if (_wcsnicmp((*sShareArg).c_str(), sMatchArg, _countof(sMatchArg) - 1) == 0 || - _wcsnicmp((*sShareArg).c_str(), sNoMatchArg, _countof(sNoMatchArg) - 1) == 0) + if (_wcsnicmp(oShareArg.c_str(), sMatchArg, _countof(sMatchArg) - 1) == 0 || + _wcsnicmp(oShareArg.c_str(), sNoMatchArg, _countof(sNoMatchArg) - 1) == 0) { // split the NOMATCH/MATCH= sub parameter to get the regular expression part - std::vector oMatchArgs = SplitArgs(*sShareArg, L"="); + std::vector oMatchArgs = SplitArgs(oShareArg, L"="); // verify a regular expression was actually specified if (oMatchArgs.size() != 2) @@ -51,7 +50,7 @@ OperationSharePaths::OperationSharePaths(std::queue & oArgList) : try { // parse the regular expression - ((_wcsnicmp((*sShareArg).c_str(), sMatchArg, _countof(sMatchArg) - 1) == 0) ? oMatchRegex : oNoMatchRegex) = + ((_wcsnicmp(oShareArg.c_str(), sMatchArg, _countof(sMatchArg) - 1) == 0) ? oMatchRegex : oNoMatchRegex) = std::wregex(oMatchArgs[1], std::regex_constants::icase); } catch (std::exception &) @@ -61,21 +60,21 @@ OperationSharePaths::OperationSharePaths(std::queue & oArgList) : exit(-1); } } - else if (_wcsicmp((*sShareArg).c_str(), L"INCLUDEHIDDEN") == 0) + else if (_wcsicmp(oShareArg.c_str(), L"INCLUDEHIDDEN") == 0) { bHiddenIncluded = true; } - else if (_wcsicmp((*sShareArg).c_str(), L"ADMINONLY") == 0) + else if (_wcsicmp(oShareArg.c_str(), L"ADMINONLY") == 0) { bAdminOnly = true; } - else if (_wcsicmp((*sShareArg).c_str(), L"STOPONERROR") == 0) + else if (_wcsicmp(oShareArg.c_str(), L"STOPONERROR") == 0) { bStopOnErrors = true; } else { - wprintf(L"ERROR: Unrecognized share lookup option '%s'\n", (*sShareArg).c_str()); + wprintf(L"ERROR: Unrecognized share lookup option '%s'\n", oShareArg.c_str()); exit(-1); } } @@ -141,11 +140,11 @@ OperationSharePaths::OperationSharePaths(std::queue & oArgList) : // enumerate the shares and make sure there are no duplicates // or child that are contained within parent paths for (std::map::const_iterator oPathOuter = mPaths.begin(); - oPathOuter != mPaths.end(); oPathOuter++) + oPathOuter != mPaths.end(); ++oPathOuter) { bool bAddToPathList = true; - for (std::map::const_iterator oPathInner = oPathOuter; - oPathInner != mPaths.end(); oPathInner++) + for (auto oPathInner = oPathOuter; + oPathInner != mPaths.end(); ++oPathInner) { // see if the path is a sub-path of another path if (oPathInner->first != oPathOuter->first && diff --git a/OperationSidHistory.cpp b/OperationSidHistory.cpp index af65362..d5a282c 100644 --- a/OperationSidHistory.cpp +++ b/OperationSidHistory.cpp @@ -19,7 +19,7 @@ SidActionResult OperationSidHistory::DetermineSid(WCHAR * const sSdPart, ObjectE // lookup the textual name for this account and // return if it is not found std::wstring sAccountName = GetNameFromSid(tCurrentSid, NULL); - if (sAccountName == L"") return SidActionResult::Nothing; + if (sAccountName.empty()) return SidActionResult::Nothing; // now do a forward lookup on that same account name to see what the // primary sid for the account actually is diff --git a/Resource.rc b/Resource.rc index 69dbc99..541e116 100644 Binary files a/Resource.rc and b/Resource.rc differ diff --git a/Version.h b/Version.h index 1ed8185..e4cef6f 100644 --- a/Version.h +++ b/Version.h @@ -1,4 +1,4 @@ #pragma once -#define VERSION_STRING "1.9.3.0" -#define VERSION_COMMA 1,9,3,0 +#define VERSION_STRING "1.10.0.0" +#define VERSION_COMMA 1,10,0,0 diff --git a/repacls.vcxproj b/repacls.vcxproj index f29df9c..29a3a0d 100644 --- a/repacls.vcxproj +++ b/repacls.vcxproj @@ -203,6 +203,8 @@ + + @@ -228,10 +230,12 @@ + + diff --git a/repacls.vcxproj.filters b/repacls.vcxproj.filters index d592a39..935635b 100644 --- a/repacls.vcxproj.filters +++ b/repacls.vcxproj.filters @@ -103,6 +103,12 @@ Source\Operations + + Source\Operations + + + Source\Operations + @@ -222,6 +228,12 @@ Includes\Operations + + Includes\Operations + + + Includes\Operations +