/**
* This code is by OShah. all code will have the following licence.
* Copyright Shexec32. All code bears the following licence:
**/
/**
* Copyright Shexec32 2004-2005. All rights reserved.
*
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
* ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
* PARTICULAR PURPOSE.
**/
#include "SecurityCore.h"
#include "resource2.h"
/* Contains the core security functions and methods. */
BOOL __stdcall DoSecurityBox(HWND TheirhWnd, SE_OBJECT_TYPE seObjType,
const std::list<const std::basic_string<TCHAR> > &FileNamesIn, BOOL AddWritability /* = FALSE */)
{/** The API to actually call the ACLUI box. This API forms the root of all our program.
*
**/
if( (TheirhWnd != NULL && !::IsWindow(TheirhWnd)) ||
(::IsBadReadPtr(&FileNamesIn, sizeof(std::list<const std::basic_string<TCHAR> > ))) ||
FileNamesIn.size() < 1)
{/* As an API, validate inputs. */
::SetLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
/* TODO: We currently only support file objects. Change this. */
seObjType = SE_FILE_OBJECT;
if( FileNamesIn.size() < 1)
return FALSE;
std::list<const std::basic_string<TCHAR> > FileNames = FileNamesIn;
for(std::list<const std::basic_string<TCHAR> >::iterator Iter = FileNames.begin();
Iter != FileNames.end(); Iter++)
{/* Convert all filenames to full paths */
sized_array<TCHAR> FileName (Iter->size() + 1 + FILENAME_MAX);
FileName = *Iter;
if(Iter->size() < 1 ||
::GetFileAttributes(Iter->c_str()) == INVALID_FILE_ATTRIBUTES )
{/* skip any non valid filenames. */
const std::list< const std::basic_string<TCHAR> >::iterator eraseIter = Iter;
FileNames.erase(eraseIter);
if(FileNames.empty()) break;
else continue;
}
/* convert relative paths to full paths. */
if(::_wfullpath(FileName.get(), Iter->c_str(), FILENAME_MAX + 1) != NULL)
Iter->assign(FileName.get());
}
if(FileNames.size() < 1)
{/* We've determined the list contains all invalid names */
int mBoxRes = ::MessageBox(TheirhWnd, _T("None of the objects apparently exist. Do you want to continue? ")
_T("(may cause a crash)"), _T("File Not Found."), MB_ICONEXCLAMATION | MB_APPLMODAL | MB_YESNO | MB_DEFBUTTON1);
if(mBoxRes != IDYES) return TRUE;
FileNames.push_back(_T(""));
}
{/* ASSUMPTION: CoInitialize() has been called (FilePermsbox calls it at the start). */
std::auto_ptr<CObjSecurity> SecPage;
try {
SecPage.reset(new CObjSecurity(FileNames, seObjType, TheirhWnd));
if(AddWritability == TRUE)
{/* Test if we have the "SeSecurityPrivilege". */
PrivMgrImpl PrivClassImpl;
{
PrivMgr SeAutoPrivObj(SE_SECURITY_NAME, &PrivClassImpl);
SecPage->MakeWritable(TRUE);
if(SeAutoPrivObj.TurnOn() != TRUE)
SecPage->DisableSacl();
}
}
::EditSecurity(TheirhWnd, SecPage.get());
} catch(const std::basic_string<TCHAR> &e) {/* We know the error here, tell the user. */
while(SecPage->get_currenthWnd() != TheirhWnd)
{/* Close all our windows. */
SecPage->CloseCurrenthWnd();
}
::MessageBox(TheirhWnd, e.c_str(),
_T("Unhandled Error."), MB_ICONEXCLAMATION | MB_OK);
return FALSE;
}
}
return TRUE;
}
INT_PTR CALLBACK TreeErrorFunc(HWND TheirhWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{/** This dialog box is shown when the system has a problem resetting the tree for security info.
* It presents 4 options: Stop, Retry, Ignore, Ignore All.
**/
switch(Msg)
{
case WM_INITDIALOG:
{/* Access the structure in lParam. */
const TCHAR *TError = reinterpret_cast<const TCHAR *>(lParam);
if(!::IsBadStringPtr(TError,1))
{
::SetDlgItemText(TheirhWnd, IDC_EDIT1, TError);
}
::SetWindowText(TheirhWnd, _T("FilePermsBox - Error Setting Security."));
return TRUE;
}
case WM_CLOSE:
{/* By default, close means IDCANCEL (Hitting enter will send a WM_COMMAND message). */
::EndDialog(TheirhWnd, IDCANCEL);
return TRUE;
}
case WM_COMMAND:
{/* End Dialog with the IDCONTROL */
switch(LOWORD(wParam))
{
case IDCANCEL:/* Stop */
case IDRETRY:/* Retry */
case IDIGNORE:/* Ignore */
case IDC_BUTTON1:/* Ignore All */
{/* in each case, close this box. */
::EndDialog(TheirhWnd, LOWORD(wParam));
return FALSE;
}
default:
return FALSE;
}
}
default:
break;
}
return FALSE;
}
VOID WINAPI TreeCallBackFunc(wchar_t *pObjectName, DWORD Status, PROG_INVOKE_SETTING *pInvokeSetting,
void *PtrToThis, BOOL SecuritySet)
{/** This callback function is called with TreeSetNamedSecurityInfo(). It tells us what happened with this case.
* We can extract the Object this belongs to in ThisClass (so use it).
* Input: pObjectName - Name of the file.
* Status - The Last Error (GetLastError code).
* pInvokeSetting - We've set this to ProgressInvokeEveryObject.
* PtrToThis - Dereference and cast PtrToThis to CSecurityObj to get the class back.
* SecuritySet - if FALSE. We'll need to do something
* Output: No return value, but we are expected to alter pInvokeSetting.
**/
CObjSecurity *ThisClass = reinterpret_cast<CObjSecurity *>(PtrToThis);
if(::IsBadReadPtr(PtrToThis, sizeof(CObjSecurity)))
throw STD(_T("A pointer was not properly passed to a callback"));
/* We need the ThisClass pointer regardless of whether security was set. */
/* Later on, we need to sync ThisClass->pInvokeSetting with the pInvokeSetting set here. */
if(SecuritySet != TRUE)
{/* Something occurred. Inform the User. */
INT_PTR Choice = IDIGNORE;
std::basic_stringstream<TCHAR> TError;
TError << _T("Error setting security on:\n") << pObjectName << _T("\nThe error code was ") << Status << _T(": ")
<< static_cast<const TCHAR *>(::GetLastErrorText(Status)) << _T(". Would you like to continue?");
if(ThisClass->pInvokeSetting != ProgressInvokeNever)
{
/* Put the mouse cursor back to normal. */
HANDLE hCursor = ::LoadImage(NULL, MAKEINTRESOURCE(OCR_NORMAL), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
if(hCursor != NULL)/* If we couldn't load the image, just ignore the cursor stuff. */
SetClassLongPtr(ThisClass->get_currenthWnd(), GCLP_HCURSOR, LONG_PTR2(hCursor));
Choice = ::DialogBoxParam(g_hInst, MAKEINTRESOURCE(IDD_DIALOG2), ThisClass->get_currenthWnd(),
TreeErrorFunc, reinterpret_cast<LPARAM>(TError.str().c_str()));
/* Re-hourglass the mouse after the user responded. */
hCursor = ::LoadImage(NULL, MAKEINTRESOURCE(OCR_APPSTARTING), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
if(hCursor != NULL)/* If we couldn't load the image, just ignore the cursor stuff. */
SetClassLongPtr(ThisClass->get_currenthWnd(), GCLP_HCURSOR, LONG_PTR2(hCursor));
}
switch(Choice)
{/* Choice will be one of: IDCANCEL, IDRETRY, IDIGNORE, IDC_BUTTON1 */
default:/* By default, cancel. */
case IDCANCEL:
{/* Set pInvokeSetting according to result */
*pInvokeSetting = ProgressCancelOperation;
ThisClass->pInvokeSetting = *pInvokeSetting;
break;
}
case IDRETRY:
{/* This function is to be recalled */
*pInvokeSetting = ProgressRetryOperation;
break;
}
case IDIGNORE:
{
*pInvokeSetting = ProgressInvokeEveryObject;
ThisClass->pInvokeSetting = *pInvokeSetting;
break;
}
case IDC_BUTTON1:
{/* Ignore All. Never show the error again. */
*pInvokeSetting = ProgressInvokeEveryObject;
ThisClass->pInvokeSetting = ProgressInvokeNever;
break;
}
}
}
else
{/* Sanity change pInvokeSetting to default object. TreeResetNamedSecurityInfo may have changed it. */
*pInvokeSetting = ProgressInvokeEveryObject;
/* Post a message to the Progress Window. */
}
if(*pInvokeSetting != ProgressRetryOperation)
{/* If we aren't retrying this operation, set the progress bar */
if(!ThisClass->CProgressObj.IncProgressBar(pObjectName))
{/* The user wanted to cancel. Cancel. */
*pInvokeSetting = ProgressCancelOperation;
ThisClass->pInvokeSetting = *pInvokeSetting;
}
}
}
CObjSecurity::CObjSecurity(const std::list< const std::basic_string<TCHAR> > &ObjName,
SE_OBJECT_TYPE seObjType /*= SE_FILE_OBJECT */, HWND TheirhWnd /* = NULL */) :
m_cRef(1), KeepExplicit(2), pfnTreeResetNamedSecurityInfo(NULL), hAdvApi32(NULL), ClasshWnd(TheirhWnd),
CProgressObj(), SeObjectType(seObjType), m_dwSIFlags ( SI_READONLY | SI_ADVANCED | SI_EDIT_ALL |
SI_EDIT_EFFECTIVE | SI_OWNER_READONLY | SI_RESET), pInvokeSetting(ProgressInvokeEveryObject),
PrivClassImpl(), ServerName(0)
{/** The Constructor. **/
hAdvApi32 = ::LoadLibrary(_T("ADVAPI32"));
pfnTreeResetNamedSecurityInfo = reinterpret_cast<DWORD (WINAPI *)(LPTSTR, SE_OBJECT_TYPE,
SECURITY_INFORMATION, PSID, PSID, PACL, PACL, BOOL, FN_PROGRESS, PROG_INVOKE_SETTING, PVOID)>
(::GetProcAddress(hAdvApi32, "TreeResetNamedSecurityInfoW"));
if(ObjName.size() == 0)
{
throw STD(_T("The list of filenames is empty."));
}
for(std::list<const std::basic_string<TCHAR> >::const_iterator Iter = ObjName.begin();
Iter != ObjName.end(); Iter++)
{/** We are given STL strings in ObjName, but our class only holds sized_arrays, so we need to marshal
* a tstring-2-sized_array converter for each object in the collection. That's this loop.
**/
m_pszObjectName.push_back(static_cast<sized_array<TCHAR> >(*Iter));
std::list<sized_array<TCHAR> >::const_iterator Iter2 = m_pszObjectName.end();
Iter2--;
if(this->IsContainer(Iter2->get()))
{/* Set folders as containers. */
if(!(m_dwSIFlags & SI_CONTAINER) && Iter != ObjName.begin())
{/* One of these folders is of multiple type. Just use the first one */
std::list<sized_array<TCHAR> >::iterator Iter3 = this->m_pszObjectName.begin();
Iter3++;
this->m_pszObjectName.erase(Iter3, this->m_pszObjectName.end());
break;
}
this->m_dwSIFlags = this->m_dwSIFlags | SI_CONTAINER | SI_RESET_DACL_TREE | SI_RESET_SACL_TREE | SI_OWNER_RECURSE;
}
}
#ifdef _DEBUG
pfnTreeResetNamedSecurityInfo = NULL;
#endif /* _DEBUG. Win2k Emulator. Uncomment the previous line to revert to Win2k behaviour. */
this->GetServerNameForFile(ObjName.front());
::InterlockedIncrement(const_cast<volatile LONG *>(reinterpret_cast<LONG *>(&g_RefCount)));
}
CObjSecurity & CObjSecurity::operator=(const CObjSecurity &OldClass)
{/* Custom methods. Equality Operator. */
if(this == &OldClass) return *this;
this->m_cRef++; /* Private for all classes */
this->m_dwSIFlags = OldClass.m_dwSIFlags;
this->m_pszObjectName.resize(OldClass.m_pszObjectName.size());
this->ClasshWnd = OldClass.ClasshWnd;
this->pfnTreeResetNamedSecurityInfo = OldClass.pfnTreeResetNamedSecurityInfo;
this->KeepExplicit = OldClass.KeepExplicit;
this->CProgressObj = OldClass.CProgressObj;
//if(this->CProgressObj != NULL)
/* TODO: If you are using _com_ptr_t, do not AddRef() it. */
// CProgressObj->AddRef();
/* Increment our own reference count for AdvApi32 (Handles need to be private to the class) */
if(this->hAdvApi32 != NULL) ::FreeLibrary(this->hAdvApi32); hAdvApi32 = NULL;
this->hAdvApi32 = ::LoadLibrary(_T("ADVAPI32"));
/* Now to copy the old list into the new list */
std::list<sized_array<TCHAR> >::iterator Iter2 = this->m_pszObjectName.begin();
for(std::list<sized_array<TCHAR> >::const_iterator Iter = OldClass.m_pszObjectName.begin();
Iter != OldClass.m_pszObjectName.end(); Iter++, Iter2++)
{/* Cast the constant old array, into a string to get its size. */
*Iter2 = *Iter;
}
return *this;
}
CObjSecurity::~CObjSecurity()
{/* We need to remove the critical section, free the Advapi32 library, and decrement the reference count. */
if(hAdvApi32 != NULL)
::FreeLibrary(hAdvApi32);
//if(CProgressObj != NULL)
/* TODO: If you are using _com_ptr_t, do not use this. */
// CProgressObj->Release();
if(g_RefCount > 0)
::InterlockedDecrement(const_cast<volatile LONG *>(reinterpret_cast<LONG *>(&g_RefCount)));
}
STDMETHODIMP
CObjSecurity::MakeWritable(BOOL bSet)
{/* This method enables/disables the window according to bSet. FALSE ==> ACLUI is read only. TRUE ==> ACLUI is editable. */
if(bSet==TRUE)
{
m_dwSIFlags = m_dwSIFlags &~ (SI_READONLY | SI_OWNER_READONLY);
m_dwSIFlags = m_dwSIFlags | SI_EDIT_AUDITS | SI_EDIT_PROPERTIES | SI_EDIT_OWNER;
}
else
m_dwSIFlags = m_dwSIFlags | SI_READONLY | SI_OWNER_READONLY;
return S_OK;
}
STDMETHODIMP
CObjSecurity::DisableSacl(void)
{/* If the user cannot edit the SACL, the Audits page is useless. */
m_dwSIFlags = m_dwSIFlags &~ (SI_EDIT_AUDITS);
return S_OK;
}
HWND CObjSecurity::get_currenthWnd(void)
{/* Accessor for hWnd (a vector) */
HWND OurhWnd = ::GetLastActivePopup(this->ClasshWnd);
return OurhWnd;
}
DWORD_PTR CObjSecurity::CloseCurrenthWnd(void)
{/* Popper for hWnd. This may block the calling thread for ~2 seconds (because we need to close the 1st window). */
DWORD_PTR dwMsgResult = static_cast<DWORD_PTR>(NULL);
::SendMessageTimeout(this->get_currenthWnd(), WM_CLOSE, 0, 0, SMTO_NORMAL, 2000, &dwMsgResult);
/* Send a close to the window. */
return dwMsgResult;
}
BOOL CObjSecurity::GetTokenOwner(std::basic_string<TCHAR> &PrimaryUser)
{/** Custom method. This gets the default owner for new objects (contained in process token).
* returns TRUE for success (PrimaryUser is changed), FALSE for failure (Primary User is unchanged).
* The resultant string will be suitable for adding to an SDDL.
**/
DWORD dwLength = 0;
HANDLE hToken = NULL;
std::basic_string<TCHAR> Result = _T("O:");
LPTSTR lpTmp = NULL;
sized_array<BYTE> TokenInformation;
/* Get a token to us. */
if(::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &hToken) != TRUE) return FALSE;
::GetTokenInformation(hToken, TokenOwner, TokenInformation.get(), 0, &dwLength); /* We expect this to fail */
TokenInformation.reset(dwLength);
/* Allocate a buffer */
if(::GetTokenInformation(hToken, TokenOwner, TokenInformation.get(), dwLength, &dwLength) != TRUE)
{/* We now have the Owner SID of us (in a PSID). */
::CloseHandle(hToken);
return FALSE;
}
if(::ConvertSidToStringSid(reinterpret_cast<TOKEN_OWNER *>(TokenInformation.get())->Owner, &lpTmp) != TRUE)
{/* We need this PSID in SDDL form. */
::CloseHandle(hToken); hToken = NULL;
return FALSE;
}
Result.append(lpTmp);
::LocalFree(lpTmp); lpTmp = NULL;
Result.append(_T("G:"));
/* Repeat all for Group SID. */
TokenInformation.reset();
::GetTokenInformation(hToken, TokenPrimaryGroup, TokenInformation.get(), 0, &dwLength); /* We expect this to fail */
TokenInformation.reset(dwLength);
if(::GetTokenInformation(hToken, TokenOwner, TokenInformation.get(), dwLength, &dwLength) != TRUE)
{/* Group SID */
::CloseHandle(hToken); hToken = NULL;
return FALSE;
}
if(::ConvertSidToStringSid(reinterpret_cast<TOKEN_PRIMARY_GROUP *>(TokenInformation.get())->PrimaryGroup, &lpTmp) != TRUE)
{/* Group SID */
::CloseHandle(hToken); hToken = NULL;
return FALSE;
}
Result.append(lpTmp);
::LocalFree(lpTmp); lpTmp = NULL;
::CloseHandle(hToken); hToken = NULL;
/* After Cleanup, put Result into PrimaryUser. */
PrimaryUser = Result;
return TRUE;
}
void
CObjSecurity::GetDefaultSecurity(const std::basic_string<TCHAR> &ObjName, std::basic_string<TCHAR> &PrimaryUser)
{/* Helper Virtual function that gets the default security for the object. */
/* The default security descriptor is the security descriptor for the parent. */
if(ObjName.size() > 3)
{/* Twas a file in a directory. Find the parent owner/group, and apply this security descriptor: S:ARAID:ARAI */
PSECURITY_DESCRIPTOR pSDRes = NULL;
try {/* Get the owner of the parent object. */
PSID SidOwner = NULL, SidGroup = NULL;
LPTSTR StringSid = NULL;
size_t dwLength = ObjName.find_last_of(_T("\\/"));
sized_array<TCHAR> ParentName (dwLength + 1);
/* Get the parent name of this object */
ObjName.copy(ParentName.get(), dwLength, 0);
if( ::GetNamedSecurityInfo(ParentName.get(), this->SeObjectType, OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION, &SidOwner, &SidGroup, NULL, NULL, &pSDRes) != ERROR_SUCCESS)
{/* Use the parent's security descriptor to get its owner. */
throw STD(_T("Error getting the owner of the parent object"));
}
if(::ConvertSidToStringSid(SidOwner, &StringSid) != TRUE)
{/* In SDDL. */
::LocalFree(pSDRes); pSDRes = NULL;
throw STD(_T("Error interpreting owner SID"));
}
PrimaryUser = _T("O:");
/* Put in the found owner into this SD. */
PrimaryUser.append(StringSid);
::LocalFree(StringSid); StringSid = NULL;
if(::ConvertSidToStringSid(SidGroup, &StringSid) != TRUE)
{/* And The Owner. */
::LocalFree(pSDRes); pSDRes = NULL;
throw STD(_T("Error interpreting Group SID"));
}
PrimaryUser.append(_T("G:"));
PrimaryUser.append(StringSid);
::LocalFree(StringSid); StringSid = NULL;
::LocalFree(pSDRes); pSDRes = NULL;
} catch(const std::basic_string<TCHAR> &) {/* If something went wrong, use the SID of Us. */
if(GetTokenOwner(PrimaryUser) != TRUE) PrimaryUser = _T("O:BAG:BA");
}
PrimaryUser += _T("S:ARAID:ARAI");
}
else
{/** 'Twas a root object. We'll need to apply this security descriptor:
* O:BAG:BAS:D:(A;OICI;FA;;;BA)(A;OICI;FA;;;SY)(A;OICI;0x1200a9;;;BU)(A;CI;LC;;;BU)(A;;0x1200a9;;;AU)
* (if owner shouldn't be BA, then we should use the sid of the owner).
* Our SD should be a little more secure than Windows (eg. replace Everyone with AU).
**/
if(GetTokenOwner(PrimaryUser) != TRUE) PrimaryUser = _T("O:BAG:BA");
PrimaryUser += _T("S:D:(A;OICI;FA;;;BA)(A;OICI;FA;;;SY)")
_T("(A;OICI;0x1200a9;;;BU)(A;CI;LC;;;BU)(A;;0x1200a9;;;AU)");
/** TODO: The root drive isn't the only folder that needs protection, there are other folders that
* need specific acls.
**/
::SetLastError(ERROR_SUCCESS);
}
}
BOOL
CObjSecurity::IsContainer(const std::basic_string<TCHAR> &ObjectName)
{/* Returns true if the object specified is a container. Abstract, because this is a object specific property. */
DWORD FileAttrib = ::GetFileAttributes(ObjectName.c_str());
if((FileAttrib & FILE_ATTRIBUTE_DIRECTORY) && !(FileAttrib & FILE_ATTRIBUTE_REPARSE_POINT))
return TRUE;
return FALSE;
}
BOOL
CObjSecurity::IsDuplicate(const std::basic_string<TCHAR> &Iter,
const std::list<const std::basic_string<TCHAR> > &Collection)
{/* Returns true if the object contains */
if(std::find<std::list<const std::basic_string<TCHAR> >::const_iterator,
const std::basic_string<TCHAR> >(Collection.begin(), Collection.end(), Iter) == Collection.end())
{
return FALSE;
}
return TRUE;
}
DWORD
CObjSecurity::RefreshShell(BOOL IsContainer)
{/* Informs the shell that there are new files to handle (if we have updated the security descriptor). */
if(IsContainer != FALSE)
{
std::list<sized_array<TCHAR> >::const_iterator Iter = this->m_pszObjectName.begin();
::SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_PATH, Iter->get(), NULL);
}
::SetLastError(ERROR_SUCCESS);
DWORD dwErr = ERROR_SUCCESS;
return dwErr;
}
BOOL
CObjSecurity::GetServerNameForFile(const std::basic_string<TCHAR> &FileNameIn)
{/* Given the specified full filename, returns the computer name of that file in the class member ServerName. */
if(FileNameIn.size() == 0)
return FALSE;
/* Get just the first character of FileNameIn, and append trailing slashes */
std::basic_string<TCHAR> DriveName = FileNameIn.substr(0, 1);
DriveName.append(_T(":"));
/* If we aren't remote, use NULL as the text. */
UINT drvType = ::GetDriveType(DriveName.c_str());
if(!(drvType & DRIVE_REMOTE)) return FALSE;
/* Okay we are remote: use the WNET functions to find out our server name. */
DWORD dwSize = 0;
::WNetGetConnection(DriveName.c_str(), NULL, &dwSize);
/* This is expected to fail with ERROR_INSUFFICIENT_BUFFER */
sized_array<TCHAR> TargetPath(dwSize);
/* We need a movable pointer to this block of data */
if(::WNetGetConnection(DriveName.c_str(), TargetPath.get(), &dwSize) != NO_ERROR)
{/* in case of failure, use NULL as the server */
return FALSE;
}
/* Copy it again to a basic_string. */
DriveName = TargetPath.get();
/* Get the bits between "\\" and "\" */
const std::basic_string<TCHAR>::size_type start = DriveName.find_first_not_of(_T("\\/:"));
if(start > DriveName.size())
return FALSE;
const std::basic_string<TCHAR>::size_type end = DriveName.find_first_of(_T("\\/"), start);
DriveName = DriveName.substr(start, end - start);
/* That's the finished computer name. */
this->ServerName = DriveName;
return TRUE;
}