//#pragma title( "SDResolve.cpp - SDResolve: A Domain Migration Utility" ) /* Copyright (c) 1995-1998, Mission Critical Software, Inc. All rights reserved. =============================================================================== Module - sdresolve.cpp System - SDResolve Author - Christy Boles Created - 97/07/11 Description - Routines to iterate through files, shares, and printers when processing security on a machine. Updates - =============================================================================== */ #include "stdafx.h" #include #include #include #include #include #include "Common.hpp" #include "ErrDct.hpp" #include "UString.hpp" #include "sd.hpp" #include "sidcache.hpp" #include "enumvols.hpp" #include "SecObj.hpp" #include "ealen.hpp" #include "BkupRstr.hpp" #include "TxtSid.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif bool enforce; extern TErrorDct err; extern bool silent; extern bool IsMachineName(const LPWSTR name); extern bool IsShareName(const LPWSTR name); extern bool ContainsWildcard( WCHAR const * name); #define MAX_BUFFER_LENGTH 10000 #define PRINT_BUFFER_SIZE 2000 //****************************************************************************************************** // Main routine for SDResolve // Iterates files and directories to be resolved void IteratePath( WCHAR * path, // in -path to start iterating from SecurityTranslatorArgs * args, // in -translation settings TSDResolveStats * stats, // in -stats (to display pathnames & pass to ResolveSD) TSecurableObject * LC, // in -last container TSecurableObject * LL, // in -last file bool haswc // in -indicates whether path contains a wc character ) { HANDLE hFind; WIN32_FIND_DATA findEntry; BOOL b; TFileSD * currSD; bool changeLastCont; bool changeLastLeaf; WCHAR * appendPath = NULL; WCHAR safepath[LEN_Path + 10]; TFileSD * LastContain = (TFileSD*) LC; TFileSD * LastLeaf = (TFileSD*) LL; WCHAR localPath[LEN_Path]; // this is the first (for this) dir safecopy(safepath,path); safecopy(localPath,path); // Check to see if path is longer than MAX_PATH // if so, add \\?\ to the beginning of it to // turn off path parsing if ( UStrLen(path) >= MAX_PATH && path[2] != L'?' ) { WCHAR temp[LEN_Path]; if ( (path[0] == L'\\') && (path[1] == L'\\') ) // UNC name { UStrCpy(temp,L"\\\\?\\UNC\\"); } else { UStrCpy(temp,L"\\\\?\\"); } UStrCpy(temp + UStrLen(temp),path); safecopy(localPath,temp); } appendPath = localPath + UStrLen(localPath); if ( *(appendPath-1) == L'\\' ) // if there's already a backslash on the end of the path, don't add another one appendPath--; if ( ! haswc ) UStrCpy(appendPath, "\\*.*"); if ( args->LogVerbose() ) err.DbgMsgWrite(0,L"Starting IteratePath: %ls",path); for ( b = ((hFind = FindFirstFile(localPath, &findEntry)) != INVALID_HANDLE_VALUE) ; b ; b = FindNextFile(hFind, &findEntry) ) { if ( ! haswc) appendPath[1] = '\0'; // restore path -- remove \*.* append if ( ! UStrCmp((LPWSTR)findEntry.cFileName,L".") || ! UStrCmp((LPWSTR)findEntry.cFileName,L"..") ) continue; // ignore names '.' and '..' if ( ! haswc ) UStrCpy(appendPath+1, findEntry.cFileName); else { for ( WCHAR * ch = appendPath-1; ch >= path && *ch != L'\\' ; ch-- ) ; UStrCpy(ch+1,findEntry.cFileName); } if ( ((TAccountCache *)args->Cache())->IsCancelled() ) { break; } currSD = new TFileSD(localPath); stats->DisplayPath(localPath); if ( !currSD || !currSD->HasSecurity() ) { //err.MsgWrite(0,"Error: Couldn't get the SD"); } else { if ( findEntry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) // if dir { // resolve this container & iterate next container changeLastCont = currSD->ResolveSD(args,stats,directory,LastContain); if ( changeLastCont ) { if ( LastContain && LastContain != LC ) { delete LastContain; } LastContain = currSD; } else { delete currSD; } IteratePath(localPath,args,stats,LastContain,LastLeaf,false); } else { // iterate this file with last changeLastLeaf = currSD->ResolveSD(args,stats,file,LastLeaf); if ( changeLastLeaf ) { if ( LastLeaf && LastLeaf != LL ) { delete LastLeaf; } LastLeaf = currSD; } else { delete currSD; } } } } if ( LastContain && LastContain != LC ) { delete LastContain; } if ( LastLeaf && LastLeaf != LL ) { delete LastLeaf; } appendPath[0] = '\0'; DWORD rc = GetLastError(); if ( args->LogVerbose() ) err.DbgMsgWrite(0,L"Closing IteratePath %S",safepath); FindClose(hFind); switch ( rc ) { case ERROR_NO_MORE_FILES: case 0: break; default: err.SysMsgWrite(ErrE, rc, DCT_MSG_FIND_FILE_FAILED_SD, path, rc); } return; } DWORD ResolvePrinter( PRINTER_INFO_4 * pPrinter, // in - printer information SecurityTranslatorArgs * args, // in - translation settings TSDResolveStats * stats // in - stats ) { DWORD rc = 0; // DWORD needed = 0; TPrintSD sd(pPrinter->pPrinterName); if ( sd.GetSecurity() ) { sd.ResolveSD(args,stats,printer,NULL); } return rc; } int ServerResolvePrinters( WCHAR const * server, // in -translate the printers on this server SecurityTranslatorArgs * args, // in -translation settings TSDResolveStats * stats // in -stats ) { DWORD rc = 0; PRINTER_INFO_4 * pInfo = NULL; BYTE * buffer = new BYTE[PRINT_BUFFER_SIZE]; DWORD cbNeeded = PRINT_BUFFER_SIZE; DWORD nReturned = 0; if (!buffer) return ERROR_NOT_ENOUGH_MEMORY; if (! EnumPrinters(PRINTER_ENUM_LOCAL,NULL,4,buffer,PRINT_BUFFER_SIZE,&cbNeeded,&nReturned) ) { rc = GetLastError(); if ( rc == ERROR_INSUFFICIENT_BUFFER ) { // try again with a bigger buffer size delete buffer; buffer = new BYTE[cbNeeded]; if (!buffer) return ERROR_NOT_ENOUGH_MEMORY; if (! EnumPrinters(PRINTER_ENUM_LOCAL,NULL,4,buffer,cbNeeded,&cbNeeded,&nReturned) ) { rc = GetLastError(); } } } if ( ! rc ) { pInfo = (PRINTER_INFO_4 *)buffer; for ( DWORD i = 0 ; i < nReturned ; i++ ) { ResolvePrinter(&(pInfo[i]),args,stats); } } else { err.SysMsgWrite(ErrE,rc,DCT_MSG_ERROR_ENUMERATING_LOCAL_PRINTERS_D,rc); } delete buffer; return rc; } int ServerResolveShares( WCHAR const * server, // in -enumerate and translate the shares on this server SecurityTranslatorArgs * args, // in -translation settings TSDResolveStats * stats // in -stats (to display pathnames & pass to ResolveSD) ) { DWORD rc = 0; DWORD numRead = 0; DWORD totalEntries = 0; DWORD resumeHandle = 0; SHARE_INFO_0 * bufPtr = NULL; WCHAR serverName[LEN_Computer]; WCHAR fullPath[LEN_Path]; WCHAR * pServerName = serverName; DWORD ttlRead = 0; if ( server ) { safecopy(serverName,server); } else { pServerName = NULL; } do { rc = NetShareEnum(pServerName,0,(LPBYTE *)&bufPtr,MAX_BUFFER_LENGTH,&numRead,&totalEntries,&resumeHandle); if ( ! rc || rc == ERROR_MORE_DATA ) { for ( UINT i = 0 ; i < numRead ; i++ ) { // Process the SD if ( pServerName ) { swprintf(fullPath,L"%s\\%s",pServerName,bufPtr[i].shi0_netname); } else { swprintf(fullPath,L"%s",bufPtr[i].shi0_netname); } TShareSD tSD(fullPath); if ( tSD.HasSecurity() ) { stats->DisplayPath(fullPath,TRUE); tSD.ResolveSD(args,stats,share,NULL); } } ttlRead += numRead; resumeHandle = ttlRead; NetApiBufferFree(bufPtr); } } while ( rc == ERROR_MORE_DATA && numRead < totalEntries ); if ( rc && rc != ERROR_MORE_DATA ) err.SysMsgWrite(ErrE,rc,DCT_MSG_SHARE_ENUM_FAILED_SD,server,rc); return rc; } void ResolveFilePath( SecurityTranslatorArgs * args, // in - translation options TSDResolveStats * Stats, // in - class to display stats WCHAR * path, // in - path name bool validAlone, // in - whether this object exists (false for share names and volume roots) bool containsWC, // in - true if path contains wildcard bool iscontainer // in - whether the starting path is a container ) { TFileSD * pSD; if ( args->LogVerbose() ) err.MsgWrite(0,DCT_MSG_PROCESSING_S,path); Stats->DisplayPath(path); if ( validAlone && ! containsWC ) { pSD = new TFileSD(path); if (!pSD) return; if ( pSD->HasSecurity() ) pSD->ResolveSD(args, Stats, iscontainer?directory:file, NULL); delete pSD; } if ( iscontainer || containsWC ) { IteratePath(path, args, Stats, NULL, NULL, containsWC); } if ( args->Cache()->IsCancelled() ) { err.MsgWrite(0,DCT_MSG_OPERATION_ABORTED); } } void WriteOptions(SecurityTranslatorArgs * args) { //* WCHAR cmd[1000] = L"SecurityTranslation ";; WCHAR cmd[1000]; WCHAR arg[300]; UStrCpy(cmd, GET_STRING(IDS_STOptions_Start)); if ( args->NoChange() ) { //* UStrCpy(cmd +UStrLen(cmd), L"WriteChanges:No "); UStrCpy(cmd +UStrLen(cmd), GET_STRING(IDS_STOptions_WriteChng)); } if ( args->TranslateFiles() ) { //* UStrCpy(cmd +UStrLen(cmd), L"Files:Yes "); UStrCpy(cmd +UStrLen(cmd), GET_STRING(IDS_STOptions_Files)); } if ( args->TranslateShares() ) { //* UStrCpy(cmd + UStrLen(cmd),L"Shares:Yes "); UStrCpy(cmd + UStrLen(cmd),GET_STRING(IDS_STOptions_Shares)); } if ( args->TranslateLocalGroups() ) { //* UStrCpy(cmd + UStrLen(cmd),L"LGroups:Yes "); UStrCpy(cmd + UStrLen(cmd),GET_STRING(IDS_STOptions_LocalGroup)); } if ( args->TranslateUserRights() ) { // UStrCpy(cmd + UStrLen(cmd),L"UserRights:Yes "); UStrCpy(cmd + UStrLen(cmd),GET_STRING(IDS_STOptions_URights)); } if ( args->TranslatePrinters() ) { // UStrCpy(cmd + UStrLen(cmd),L"UserRights:Yes "); UStrCpy(cmd + UStrLen(cmd),GET_STRING(IDS_STOptions_Printers)); } if ( args->TranslateUserProfiles() ) { //* UStrCpy(cmd + UStrLen(cmd),L"Profiles:Yes "); UStrCpy(cmd + UStrLen(cmd),GET_STRING(IDS_STOptions_Profiles)); } if ( args->TranslateRecycler() ) { //* UStrCpy(cmd + UStrLen(cmd),L"RecycleBin:Yes "); UStrCpy(cmd + UStrLen(cmd),GET_STRING(IDS_STOptions_RBin)); } if ( *args->LogFile() ) { //* wsprintf(arg,L"LogFile:%S ",args->LogFile()); wsprintf(arg,GET_STRING(IDS_STOptions_LogName),args->LogFile()); UStrCpy(cmd +UStrLen(cmd), arg); } if ( args->TranslationMode() == ADD_SECURITY ) { //* UStrCpy(cmd +UStrLen(cmd), L"TranslationMode:Add "); UStrCpy(cmd +UStrLen(cmd), GET_STRING(IDS_STOptions_AddMode)); } else if ( args->TranslationMode() == REMOVE_SECURITY ) { //* UStrCpy(cmd +UStrLen(cmd), L"TranslationMode:Remove "); UStrCpy(cmd +UStrLen(cmd), GET_STRING(IDS_STOptions_RemoveMode)); } else { //* UStrCpy(cmd + UStrLen(cmd),L"TranslationMode:Replace "); UStrCpy(cmd + UStrLen(cmd),GET_STRING(IDS_STOptions_ReplaceMode)); } wsprintf(arg,L"%s %s ",args->Source(), args->Target()); UStrCpy(cmd +UStrLen(cmd), arg); err.MsgWrite(0,DCT_MSG_GENERIC_S,&*cmd); } void TranslateRecycler( SecurityTranslatorArgs * args, // in - translation options TSDResolveStats * Stats, // in - class to display stats WCHAR * path // in - drive name ) { err.MsgWrite(0,DCT_MSG_PROCESSING_RECYCLER_S,path); WCHAR folder[LEN_Path]; WCHAR const * recycler = L"RECYCLER"; WCHAR strSid[200]; WCHAR srcPath[LEN_Path]; WCHAR tgtPath[LEN_Path]; DWORD lenStrSid = DIM(strSid); _wfinddata_t fData; // long hRecycler; LONG_PTR hRecycler; PSID pSidSrc = NULL, pSidTgt = NULL; TRidNode * pNode; DWORD rc = 0; swprintf(folder,L"%s\\%s\\*",path,recycler); long mode = args->TranslationMode(); // Windows 2000 checks the SD for the recycle bin when the recycle bin is opened. If the SD does not match the // default template (permissions for user, admin, and system), Windows displays a message that the recycle bin is corrupt. // This change may also be in NT 4 SP 7. To avoid causing this corrupt recycle bin message, we will always translate the // recycle bins in replace mode. We will not change if we are doing a remove. if (args->TranslationMode() != REMOVE_SECURITY) args->SetTranslationMode(REPLACE_SECURITY); // use _wfind to look for hidden files in the folder for ( hRecycler = _wfindfirst(folder,&fData) ; hRecycler != -1 && ( rc == 0 ); rc = (DWORD)_wfindnext(hRecycler,&fData) ) { pSidSrc = SidFromString(fData.name); if ( pSidSrc ) { err.MsgWrite(0,DCT_MSG_PROCESSING_RECYCLE_FOLDER_S,fData.name); pNode = (TRidNode*)args->Cache()->Lookup(pSidSrc); if ( pNode && pNode != (TRidNode*)-1 ) { pSidTgt = args->Cache()->GetTgtSid(pNode); // get the target directory name GetTextualSid(pSidTgt,strSid,&lenStrSid); if ( args->LogVerbose() ) err.DbgMsgWrite(0,L"Target sid is: %ls",strSid); if ( ! args->NoChange() && args->TranslationMode() != REMOVE_SECURITY ) { // rename the directory swprintf(srcPath,L"%s\\%s\\%s",path,recycler,fData.name); swprintf(tgtPath,L"%s\\%s\\%s",path,recycler,strSid); if ( ! MoveFile(srcPath,tgtPath) ) { rc = GetLastError(); if ( (rc == ERROR_ALREADY_EXISTS) && (args->TranslationMode() == REPLACE_SECURITY) ) { // the target recycle bin already exists // attempt to rename it with a suffix, so we can rename the new bin to the SID WCHAR tmpPath[LEN_Path]; long ndx = 0; do { swprintf(tmpPath,L"%ls%ls%ld",tgtPath,GET_STRING(IDS_RenamedRecyclerSuffix),ndx); if (! MoveFile(tgtPath,tmpPath) ) { rc = GetLastError(); ndx++; } else { rc = 0; err.MsgWrite(0,DCT_MSG_RECYCLER_RENAMED_SS,tgtPath,tmpPath); } } while ( rc == ERROR_ALREADY_EXISTS ); if ( ! rc ) { // we have moved the pre-existing target recycler out of the way // now retry the rename if (! MoveFile(srcPath,tgtPath) ) { err.SysMsgWrite(ErrE,rc,DCT_MSG_RECYCLER_RENAME_FAILED_SD,pNode->GetAcctName(),rc); } else { err.MsgWrite(0,DCT_MSG_RECYCLER_RENAMED_SS,srcPath,tgtPath); // run security translation on the new folder ResolveFilePath(args,Stats,tgtPath,TRUE,FALSE,TRUE); } } else { err.SysMsgWrite(ErrE,rc,DCT_MSG_RECYCLER_RENAME_FAILED_SD,pNode->GetAcctName(),rc); } } else { err.SysMsgWrite(ErrE,rc,DCT_MSG_RECYCLER_RENAME_FAILED_SD,pNode->GetAcctName(),rc); } } else { err.MsgWrite(0,DCT_MSG_RECYCLER_RENAMED_SS,srcPath,tgtPath); // run security translation on the new folder ResolveFilePath(args,Stats,tgtPath,TRUE,FALSE,TRUE); } } FreeSid(pSidTgt); } FreeSid(pSidSrc); } } // set the translation mode back to its original value args->SetTranslationMode(mode); } // if the specified node is a normal share, this attempts to convert it to a path // using the administrative shares void BuildAdminPathForShare( TPathNode * tnode, WCHAR * adminShare ) { // if all else fails, return the same name as specified in the node UStrCpy(adminShare,tnode->GetPathName()); SHARE_INFO_502 * shInfo = NULL; DWORD rc = 0; WCHAR shareName[LEN_Path]; WCHAR * slash = NULL; UStrCpy(shareName,tnode->GetPathName() + UStrLen(tnode->GetServerName()) +1); slash = wcschr(shareName,L'\\'); if ( slash ) *slash = 0; rc = NetShareGetInfo(tnode->GetServerName(),shareName,502,(LPBYTE*)&shInfo); if ( ! rc ) { if ( *shInfo->shi502_path ) { // build the administrative path name for the share UStrCpy(adminShare,tnode->GetServerName()); UStrCpy(adminShare + UStrLen(adminShare),L"\\"); UStrCpy(adminShare + UStrLen(adminShare),shInfo->shi502_path); WCHAR * colon = wcschr(adminShare,L':'); if ( colon ) { *colon = L'$'; UStrCpy(adminShare + UStrLen(adminShare),L"\\"); UStrCpy(adminShare + UStrLen(adminShare),slash+1); } else { // something went wrong -- revert to the given path UStrCpy(adminShare,tnode->GetPathName()); } } NetApiBufferFree(shInfo); } } // Main routine for resolving file and directory SD's. int ResolveAll( SecurityTranslatorArgs * args, // in- translation settings TSDResolveStats * Stats // in- counts of examined, changed objects, etc. ) { WCHAR * warg; WCHAR * machine; UINT errmode; int retcode = 0; TPathNode * tnode; errmode = SetErrorMode(SEM_FAILCRITICALERRORS); if ( ! retcode ) { WriteOptions(args); Stats->InitDisplay(args->NoChange()); err.MsgWrite(0,DCT_MSG_FST_STARTING); // Process Files and Directories if (! args->IsLocalSystem() ) { TNodeListEnum tenum; for (tnode = (TPathNode *)tenum.OpenFirst((TNodeList *)args->PathList()) ; tnode ; tnode = (TPathNode *)tenum.Next() ) { DWORD rc; BOOL needToGetBR = FALSE; // BOOL abort = FALSE; // BOOL firstTime = TRUE; warg = tnode->GetPathName(); machine = GetMachineName(warg); needToGetBR = ( args->TranslateFiles() ); if ( *tnode->GetServerName() && ! args->IsLocalSystem() ) { warg = tnode->GetPathName(); err.MsgWrite(0,DCT_MSG_PROCESSING_S,warg); if ( args->TranslateFiles() ) { if ( needToGetBR ) { GetBkupRstrPriv(tnode->GetServerName()); } if ( IsMachineName(warg) ) { // need to process each drive on this machine TVolumeEnum vEnum; rc = vEnum.Open(warg,VERIFY_PERSISTENT_ACLS,args->LogVerbose()); if ( rc ) { err.SysMsgWrite(ErrE,rc,DCT_MSG_ERROR_ACCESSING_DRIVES_SD,warg,rc); } else { while ( warg = vEnum.Next() ) { ResolveFilePath(args, Stats, warg, false, // not valid alone false, // no wildcard true ); // container } warg = machine; } vEnum.Close(); } else { WCHAR adminShare[LEN_Path]; // Verify that the volume is NTFS rc = tnode->VerifyPersistentAcls(); switch ( rc ) { case ERROR_SUCCESS: // Process the path // if it's a share name, process the root of the share if( IsShareName(tnode->GetPathName()) ) { WCHAR sharePath[LEN_Path]; swprintf(sharePath,L"%s\\.",tnode->GetPathName()); TFileSD sd(sharePath); if ( sd.HasSecurity() ) { sd.ResolveSD(args, Stats, directory, NULL); } } // if this is a normal share, convert it to an administrative share // path, so that we can take advantage of backup/restore privileges BuildAdminPathForShare(tnode,adminShare); ResolveFilePath(args, Stats, adminShare, !IsShareName(tnode->GetPathName()), ContainsWildcard(tnode->GetPathName()), tnode->IsContainer() || IsShareName(tnode->GetPathName())); break; case ERROR_NO_SECURITY_ON_OBJECT: err.MsgWrite(ErrW,DCT_MSG_SKIPPING_FAT_VOLUME_S,warg); break; default: err.SysMsgWrite(ErrE,rc,DCT_MSG_SKIPPING_PATH_SD,warg,rc ); break; } } } // Process the shares for this machine if ( args->TranslateShares() ) { if ( IsMachineName(warg) ) { err.MsgWrite(0,DCT_MSG_PROCESSING_SHARES_S,tnode->GetServerName()); ServerResolveShares(tnode->GetServerName(),args,Stats); } else if ( IsShareName(warg) ) { TShareSD sd(warg); if ( sd.HasSecurity() ) { if ( args->LogVerbose() ) { err.MsgWrite(0,DCT_MSG_PROCESSING_SHARE_S,warg); } sd.ResolveSD(args, Stats, share, NULL); } } } } else { // this is a local path // Verify that the volume is NTFS DWORD rc2; GetBkupRstrPriv((WCHAR*)NULL); rc2 = tnode->VerifyPersistentAcls(); switch ( rc2 ) { case ERROR_SUCCESS: // Process the path if ( args->TranslateFiles() ) { ResolveFilePath(args, Stats, tnode->GetPathName(), true, // isValidAlone ContainsWildcard(tnode->GetPathName()), tnode->IsContainer() ); } break; case ERROR_NO_SECURITY_ON_OBJECT: err.MsgWrite(ErrW,DCT_MSG_SKIPPING_FAT_VOLUME_S,warg); break; default: err.SysMsgWrite(ErrE,rc2,DCT_MSG_SKIPPING_PATH_SD,warg,rc2 ); break; } } if ( machine ) { delete machine; machine = NULL; } } tenum.Close(); } else { // Translate the entire machine err.MsgWrite(0,DCT_MSG_LOCAL_TRANSLATION); if ( args->TranslateFiles() || args->TranslateRecycler() ) { GetBkupRstrPriv((WCHAR const*)NULL); // need to process each drive on this machine TVolumeEnum vEnum; vEnum.SetLocalMode(TRUE); DWORD rc2 = vEnum.Open(NULL,VERIFY_PERSISTENT_ACLS,args->LogVerbose()); if ( rc2 ) { err.SysMsgWrite(ErrE,rc2,DCT_MSG_ERROR_ACCESSING_LOCAL_DRIVES_D,rc2); } else { while ( warg = vEnum.Next() ) { err.MsgWrite(0,DCT_MSG_PROCESSING_S,warg); if ( args->TranslateFiles() ) { ResolveFilePath(args, Stats, warg, false, // not valid alone false, // no wildcard true ); // container } if ( args->TranslateRecycler() ) { TranslateRecycler(args,Stats,warg); } } warg = NULL; } vEnum.Close(); } if ( args->TranslateShares() ) { err.MsgWrite(0,DCT_MSG_PROCESSING_LOCAL_SHARES,NULL); ServerResolveShares(NULL,args,Stats); } if ( args->TranslatePrinters() ) { err.MsgWrite(0,DCT_MSG_PROCESSING_LOCAL_PRINTERS,NULL); ServerResolvePrinters(NULL,args,Stats); } } Stats->DisplayPath(L""); } // end if ( ! retcode) SetErrorMode(errmode); return retcode; }