You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
584 lines
15 KiB
584 lines
15 KiB
/*
|
|
* Copyright (c) 1998 Microsoft Corporation
|
|
*
|
|
* Module Name:
|
|
*
|
|
* dir.cpp
|
|
*
|
|
* Abstract:
|
|
*
|
|
* This file contains code to recursively create directories.
|
|
*
|
|
* Author:
|
|
*
|
|
* Breen Hagan (BreenH) Oct-02-98
|
|
*
|
|
* Environment:
|
|
*
|
|
* User Mode
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "logfile.h"
|
|
#include <sddl.h>
|
|
#include "Aclapi.h"
|
|
#include "Accctrl.h"
|
|
|
|
/*
|
|
* Global variables.
|
|
*/
|
|
|
|
TCHAR gszDatabaseDirectory[MAX_PATH + 1] =
|
|
_T("%SystemRoot%\\System32\\LServer");
|
|
|
|
/*
|
|
* Helper Functions.
|
|
*/
|
|
|
|
DWORD
|
|
CreateDirectoryRecursively(
|
|
IN LPCTSTR pszDirectory
|
|
)
|
|
{
|
|
TCHAR Buffer[MAX_PATH + 1];
|
|
PTCHAR p,q;
|
|
BOOL fDone, br;
|
|
DWORD dwErr;
|
|
SECURITY_ATTRIBUTES SA;
|
|
SECURITY_INFORMATION securityInfo;
|
|
PSECURITY_DESCRIPTOR pSD = NULL;
|
|
PACL pOldACL = NULL;
|
|
|
|
ACL_SIZE_INFORMATION asiAclSize;
|
|
DWORD dwBufLength=sizeof(asiAclSize);
|
|
ACCESS_ALLOWED_ACE *paaAllowedAce;
|
|
DWORD dwAcl_i;
|
|
|
|
if (_tcslen(pszDirectory) > (MAX_PATH)) {
|
|
return(ERROR_BAD_PATHNAME);
|
|
}
|
|
|
|
if (ExpandEnvironmentStrings(pszDirectory, Buffer, MAX_PATH) > MAX_PATH) {
|
|
return(ERROR_BAD_PATHNAME);
|
|
}
|
|
|
|
//
|
|
// This is a string security descriptor. Look up "Security Descriptor
|
|
// Definition Language" in MSDN for more details.
|
|
//
|
|
// This one says:
|
|
//
|
|
// D: <we are creating a DACL>
|
|
// (A; <Allow ACE>
|
|
// OICI; <Perform object and container inheritance, i.e., let files and
|
|
// directories under this one have these attributes>
|
|
// GA <Generic All Access--Full Control>
|
|
// ;;;SY) <SYSTEM>
|
|
// (A;OICI;GA;;;BA) <same for Builtin Administrators group>
|
|
// (A;OICI;GA;;;CO) <same for creator/owner>
|
|
// (A;OICI;GRGWGXDTSDCCLC;;;PU) <read access for Power Users>
|
|
//
|
|
// We'll use it below to create our directory with the right permissions.
|
|
|
|
TCHAR* pwszSD = _TEXT("D:(A;OICI;GA;;;SY)(A;OICI;GA;;;BA)(A;OICI;GA;;;CO)(A;OICI;GRGWGXDTSDCCLC;;;PU)");
|
|
|
|
SA.nLength = sizeof(SECURITY_ATTRIBUTES);
|
|
SA.bInheritHandle = FALSE;
|
|
SA.lpSecurityDescriptor = NULL;
|
|
|
|
br = ConvertStringSecurityDescriptorToSecurityDescriptor(pwszSD,
|
|
SDDL_REVISION_1, &(SA.lpSecurityDescriptor), NULL);
|
|
|
|
if (br == 0) {
|
|
dwErr = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
q = Buffer;
|
|
|
|
if (q[1] == _T(':')) {
|
|
|
|
//
|
|
// This is a "C:" style path. Put p past the colon and first
|
|
// backslash, if it exists.
|
|
//
|
|
|
|
if (q[2] == _T('\\')) {
|
|
p = &(q[3]);
|
|
} else {
|
|
p = &(q[2]);
|
|
}
|
|
} else if (q[0] == _T('\\')) {
|
|
|
|
//
|
|
// This path begins with a backslash. If the second character is
|
|
// also a backslash, this is a UNC path, which is not accepted.
|
|
//
|
|
|
|
if (q[1] == _T('\\')) {
|
|
return(ERROR_BAD_PATHNAME);
|
|
} else {
|
|
p = &(q[1]);
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// This path is a relative path from the current directory.
|
|
//
|
|
|
|
p = q;
|
|
}
|
|
|
|
q = p;
|
|
fDone = FALSE;
|
|
|
|
do {
|
|
|
|
//
|
|
// Locate the next path sep char. If there is none then
|
|
// this is the deepest level of the path.
|
|
//
|
|
|
|
p = _tcschr(q, _T('\\'));
|
|
if (p) {
|
|
*p = (TCHAR)NULL;
|
|
} else {
|
|
fDone = TRUE;
|
|
}
|
|
|
|
if(fDone == TRUE)
|
|
{
|
|
BOOL bInherit = TRUE;
|
|
//
|
|
// Create this portion of the path.
|
|
//
|
|
if(CreateDirectory(Buffer,&SA)) {
|
|
dwErr = NO_ERROR;
|
|
}
|
|
else
|
|
{
|
|
dwErr = GetLastError();
|
|
if(dwErr == ERROR_ALREADY_EXISTS) {
|
|
dwErr = NO_ERROR;
|
|
|
|
if( GetNamedSecurityInfoW( (LPWSTR)Buffer,
|
|
SE_FILE_OBJECT,
|
|
DACL_SECURITY_INFORMATION,
|
|
NULL, // psidOwner
|
|
NULL, // psidGroup
|
|
&pOldACL, // pDacl
|
|
NULL, // pSacl
|
|
&pSD ) != ERROR_SUCCESS)
|
|
{
|
|
dwErr = GetLastError();
|
|
goto cleanup;
|
|
}
|
|
|
|
if(pOldACL == NULL)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
if (GetAclInformation(pOldACL,
|
|
(LPVOID)&asiAclSize,
|
|
(DWORD)dwBufLength,
|
|
(ACL_INFORMATION_CLASS)AclSizeInformation))
|
|
{
|
|
|
|
for (dwAcl_i = 0; dwAcl_i < asiAclSize.AceCount; dwAcl_i++)
|
|
{
|
|
if(GetAce( pOldACL, dwAcl_i, (LPVOID *)&paaAllowedAce))
|
|
{
|
|
if(!(paaAllowedAce->Header.AceFlags & INHERITED_ACE))
|
|
{
|
|
//some permission already exist, we don't need to
|
|
//do anything (even if it is a different permission!)
|
|
bInherit = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bInherit)
|
|
{
|
|
// If the database had default security => inheritance from parent only then, set the security.
|
|
// No changes in case of custom security.
|
|
securityInfo = DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION;
|
|
|
|
SetFileSecurity(Buffer, securityInfo, SA.lpSecurityDescriptor);
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(CreateDirectory(Buffer, NULL)) {
|
|
dwErr = NO_ERROR;
|
|
}
|
|
else {
|
|
dwErr = GetLastError();
|
|
if(dwErr == ERROR_ALREADY_EXISTS) {
|
|
dwErr = NO_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(dwErr == NO_ERROR) {
|
|
|
|
//
|
|
// Put back the path sep and move to the next component.
|
|
//
|
|
|
|
if (!fDone) {
|
|
*p = TEXT('\\');
|
|
q = p + sizeof(TCHAR);
|
|
}
|
|
} else {
|
|
fDone = TRUE;
|
|
}
|
|
|
|
} while(!fDone);
|
|
|
|
cleanup:
|
|
|
|
LocalFree(SA.lpSecurityDescriptor);
|
|
SA.lpSecurityDescriptor = NULL;
|
|
|
|
return(dwErr);
|
|
}
|
|
|
|
BOOL
|
|
ConcatenatePaths(
|
|
IN OUT LPTSTR Target,
|
|
IN LPCTSTR Path,
|
|
IN UINT TargetBufferSize,
|
|
OUT LPUINT RequiredSize OPTIONAL
|
|
)
|
|
|
|
{
|
|
UINT TargetLength,PathLength;
|
|
BOOL TrailingBackslash,LeadingBackslash;
|
|
UINT EndingLength;
|
|
|
|
TargetLength = lstrlen(Target);
|
|
PathLength = lstrlen(Path);
|
|
|
|
//
|
|
// See whether the target has a trailing backslash.
|
|
//
|
|
if(TargetLength && (Target[TargetLength-1] == TEXT('\\'))) {
|
|
TrailingBackslash = TRUE;
|
|
TargetLength--;
|
|
} else {
|
|
TrailingBackslash = FALSE;
|
|
}
|
|
|
|
//
|
|
// See whether the path has a leading backshash.
|
|
//
|
|
if(Path[0] == TEXT('\\')) {
|
|
LeadingBackslash = TRUE;
|
|
PathLength--;
|
|
} else {
|
|
LeadingBackslash = FALSE;
|
|
}
|
|
|
|
//
|
|
// Calculate the ending length, which is equal to the sum of
|
|
// the length of the two strings modulo leading/trailing
|
|
// backslashes, plus one path separator, plus a nul.
|
|
//
|
|
EndingLength = TargetLength + PathLength + 2;
|
|
if(RequiredSize) {
|
|
*RequiredSize = EndingLength;
|
|
}
|
|
|
|
if(!LeadingBackslash && (TargetLength < TargetBufferSize)) {
|
|
Target[TargetLength++] = TEXT('\\');
|
|
}
|
|
|
|
if(TargetBufferSize > TargetLength) {
|
|
lstrcpyn(Target+TargetLength,Path,TargetBufferSize-TargetLength);
|
|
}
|
|
|
|
//
|
|
// Make sure the buffer is nul terminated in all cases.
|
|
//
|
|
if (TargetBufferSize) {
|
|
Target[TargetBufferSize-1] = 0;
|
|
}
|
|
|
|
return(EndingLength <= TargetBufferSize);
|
|
}
|
|
|
|
VOID
|
|
Delnode(
|
|
IN LPCTSTR Directory
|
|
)
|
|
{
|
|
TCHAR pszDirectory[MAX_PATH + 1];
|
|
TCHAR pszPattern[MAX_PATH + 1];
|
|
WIN32_FIND_DATA FindData;
|
|
HANDLE FindHandle;
|
|
|
|
LOGMESSAGE(_T("Delnode: Entered"));
|
|
|
|
//
|
|
// Delete each file in the given directory, then remove the directory
|
|
// itself. If any directories are encountered along the way recurse to
|
|
// delete them as they are encountered.
|
|
//
|
|
// Start by forming the search pattern, which is <currentdir>\*.
|
|
//
|
|
|
|
ExpandEnvironmentStrings(Directory, pszDirectory, MAX_PATH);
|
|
LOGMESSAGE(_T("Delnode: Deleting %s"), pszDirectory);
|
|
|
|
lstrcpyn(pszPattern, pszDirectory, MAX_PATH);
|
|
ConcatenatePaths(pszPattern, _T("*"), MAX_PATH, NULL);
|
|
|
|
//
|
|
// Start the search.
|
|
//
|
|
|
|
FindHandle = FindFirstFile(pszPattern, &FindData);
|
|
if(FindHandle != INVALID_HANDLE_VALUE) {
|
|
|
|
do {
|
|
|
|
//
|
|
// Form the full name of the file or directory we just found.
|
|
//
|
|
|
|
lstrcpyn(pszPattern, pszDirectory, MAX_PATH);
|
|
ConcatenatePaths(pszPattern, FindData.cFileName, MAX_PATH, NULL);
|
|
|
|
//
|
|
// Remove read-only atttribute if it's there.
|
|
//
|
|
|
|
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
|
|
SetFileAttributes(pszPattern, FILE_ATTRIBUTE_NORMAL);
|
|
}
|
|
|
|
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
|
|
//
|
|
// The current match is a directory. Recurse into it unless
|
|
// it's . or ...
|
|
//
|
|
|
|
if ((lstrcmp(FindData.cFileName,_T("."))) &&
|
|
(lstrcmp(FindData.cFileName,_T("..")))) {
|
|
Delnode(pszPattern);
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// The current match is not a directory -- so delete it.
|
|
//
|
|
|
|
if (!DeleteFile(pszPattern)) {
|
|
LOGMESSAGE(_T("Delnode: %s not deleted: %d"), pszPattern,
|
|
GetLastError());
|
|
}
|
|
}
|
|
|
|
} while(FindNextFile(FindHandle, &FindData));
|
|
|
|
FindClose(FindHandle);
|
|
}
|
|
|
|
//
|
|
// Remove the directory we just emptied out. Ignore errors.
|
|
//
|
|
|
|
RemoveDirectory(pszDirectory);
|
|
}
|
|
|
|
/*
|
|
* Exported Functions.
|
|
*/
|
|
|
|
/*
|
|
* CheckDatabaseDirectory()
|
|
*
|
|
* CheckDatabaseDirectory is very hardcore about which paths it will accept.
|
|
*
|
|
* Good Paths:
|
|
* <DriveLetter>:\AbsolutePathToDirectory
|
|
*
|
|
* Bad Paths:
|
|
* Any path that is not like above, AND any path in the form above that
|
|
* is not on a fixed disk (e.g. no path to a floppy, CD-ROM, network
|
|
* share).
|
|
*/
|
|
|
|
DWORD
|
|
CheckDatabaseDirectory(
|
|
IN LPCTSTR pszDatabaseDir
|
|
)
|
|
{
|
|
BOOL fBadChars;
|
|
BOOL fBadPath;
|
|
UINT DriveType;
|
|
TCHAR pszExpandedDir[MAX_PATH + 1];
|
|
|
|
LOGMESSAGE(_T("CheckDatabaseDirectory: Entered"));
|
|
LOGMESSAGE(_T("CheckDatabaseDirectory: Checking %s"), pszDatabaseDir);
|
|
|
|
//
|
|
// NULL is not accepted.
|
|
//
|
|
|
|
if (pszDatabaseDir == NULL) {
|
|
return(ERROR_INVALID_PARAMETER);
|
|
}
|
|
|
|
//
|
|
// A path greater than MAX_PATH will cause problems somewhere. This
|
|
// will also catch pathnames with no environment variables that are
|
|
// still too long.
|
|
//
|
|
|
|
if (ExpandEnvironmentStrings(pszDatabaseDir, pszExpandedDir, MAX_PATH) >
|
|
MAX_PATH) {
|
|
LOGMESSAGE(_T("CheckDatabaseDirectory: Path too long"));
|
|
return(ERROR_BAD_PATHNAME);
|
|
}
|
|
|
|
//
|
|
// A path of less than three characters can't contain "<DriveLetter>:\".
|
|
// Also, don't allow anything but a letter, a colon, and a backslash.
|
|
//
|
|
|
|
fBadPath = FALSE;
|
|
|
|
if (!fBadPath) {
|
|
fBadPath = (_tcslen(pszExpandedDir) < 3);
|
|
}
|
|
if (!fBadPath) {
|
|
fBadPath = !(_istalpha(pszExpandedDir[0]));
|
|
}
|
|
if (!fBadPath) {
|
|
fBadPath = (pszExpandedDir[1] != _T(':'));
|
|
}
|
|
if (!fBadPath) {
|
|
fBadPath = (pszExpandedDir[2] != _T('\\'));
|
|
}
|
|
|
|
if (fBadPath) {
|
|
LOGMESSAGE(_T("CheckDatabaseDirectory: Not a C:\\ style directory"));
|
|
return(ERROR_BAD_PATHNAME);
|
|
}
|
|
|
|
//
|
|
// Characters like < > * ? and , won't work. Check for that now.
|
|
// Also, check for additional colons after the first C:\....
|
|
//
|
|
|
|
fBadChars = FALSE;
|
|
|
|
if (!fBadChars) {
|
|
fBadChars = (_tcschr(pszExpandedDir, _T('<')) != NULL);
|
|
}
|
|
if (!fBadChars) {
|
|
fBadChars = (_tcschr(pszExpandedDir, _T('>')) != NULL);
|
|
}
|
|
if (!fBadChars) {
|
|
fBadChars = (_tcschr(pszExpandedDir, _T('*')) != NULL);
|
|
}
|
|
if (!fBadChars) {
|
|
fBadChars = (_tcschr(pszExpandedDir, _T('?')) != NULL);
|
|
}
|
|
if (!fBadChars) {
|
|
fBadChars = (_tcschr(&(pszExpandedDir[3]), _T(':')) != NULL);
|
|
}
|
|
|
|
if (fBadChars) {
|
|
LOGMESSAGE(_T("CheckDatabaseDirectory: Invalid characters"));
|
|
return(ERROR_BAD_PATHNAME);
|
|
}
|
|
|
|
//
|
|
// GetDriveType only works for paths in the form "C:\" or
|
|
// "C:\ExistingDir". As pszDatabaseDir probably doesn't exist, it can't
|
|
// be passed to GetDriveType. Set a NULL character passed the "C:\" to
|
|
// pass in only the drive letter.
|
|
//
|
|
|
|
pszExpandedDir[3] = (TCHAR)NULL;
|
|
DriveType = GetDriveType(pszExpandedDir);
|
|
|
|
if (DriveType == DRIVE_FIXED) {
|
|
return(NO_ERROR);
|
|
} else {
|
|
LOGMESSAGE(_T("CheckDatabaseDirectory: Bad DriveType %d"), DriveType);
|
|
return(ERROR_BAD_PATHNAME);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* CreateDatabaseDirectory()
|
|
*
|
|
* Creates the specified database directory.
|
|
*/
|
|
|
|
DWORD
|
|
CreateDatabaseDirectory(
|
|
VOID
|
|
)
|
|
{
|
|
return(CreateDirectoryRecursively(gszDatabaseDirectory));
|
|
}
|
|
|
|
/*
|
|
* GetDatabaseDirectory()
|
|
*
|
|
* Returns the current database directory.
|
|
*/
|
|
|
|
LPCTSTR
|
|
GetDatabaseDirectory(
|
|
VOID
|
|
)
|
|
{
|
|
return(gszDatabaseDirectory);
|
|
}
|
|
|
|
/*
|
|
* RemoveDatabaseDirectory()
|
|
*
|
|
* Removes the entire database directory.
|
|
*/
|
|
|
|
VOID
|
|
RemoveDatabaseDirectory(
|
|
VOID
|
|
)
|
|
{
|
|
Delnode(gszDatabaseDirectory);
|
|
}
|
|
|
|
/*
|
|
* SetDatabaseDirectory()
|
|
*
|
|
* This function assumes pszDatabaseDir has been verified by a call to
|
|
* CheckDatabaseDir(), which verifies not NULL, within MAX_PATH, and on a
|
|
* fixed hard drive.
|
|
*/
|
|
|
|
VOID
|
|
SetDatabaseDirectory(
|
|
IN LPCTSTR pszDatabaseDir
|
|
)
|
|
{
|
|
if(pszDatabaseDir && (_tcslen(pszDatabaseDir) <= MAX_PATH ))
|
|
{
|
|
_tcscpy(gszDatabaseDirectory, pszDatabaseDir);
|
|
}
|
|
}
|
|
|