windows-nt/Source/XPSP1/NT/base/fs/utils/aclconv/aclconv.cxx

1936 lines
49 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1992-2000 Microsoft Corporation
Module Name:
aclconv.cxx
Abstract:
This module contains function definitions for the ACLCONV class,
which implements conversion of Lanman 2.x ACLs into NT ACLs.
Author:
Bill McJohn (billmc) 29-Jan-1992
Revision History:
Environment:
ULIB, User Mode
--*/
#define _NTAPI_ULIB_
#include "ulib.hxx"
#include "ulibcl.hxx"
#include "array.hxx"
#include "arrayit.hxx"
#include "arg.hxx"
#include "smsg.hxx"
#include "rtmsg.h"
#include "wstring.hxx"
#include "system.hxx"
#include "aclconv.hxx"
#include "file.hxx"
#include "filestrm.hxx"
#include "logfile.hxx"
BOOLEAN
QueryFileSystemName(
IN PCWSTRING RootName,
OUT PDSTRING FileSystemName
)
/*++
Routine Description:
Determines the name of the file system on the specified volume.
Arguments:
RootName -- Supplies the name of the volume's root directory.
FileSystemName -- Receives the file system name.
Return Value:
TRUE upon successful completion.
--*/
{
WCHAR NameBuffer[8];
if( !GetVolumeInformation( RootName->GetWSTR(),
NULL,
0,
NULL,
NULL,
NULL,
NameBuffer,
8 ) ) {
return FALSE;
}
return( FileSystemName->Initialize( NameBuffer ) );
}
BOOLEAN
EnablePrivilege(
PWSTR Privilege
)
/*++
Routine Description:
This routine tries to adjust the priviliege of the current process.
Arguments:
Privilege - String with the name of the privilege to be adjusted.
Return Value:
Returns TRUE if the privilege could be adjusted.
Returns FALSE, otherwise.
--*/
{
HANDLE TokenHandle;
LUID_AND_ATTRIBUTES LuidAndAttributes;
TOKEN_PRIVILEGES TokenPrivileges;
if( !OpenProcessToken( GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
&TokenHandle ) ) {
DebugPrint( "OpenProcessToken failed" );
return( FALSE );
}
if( !LookupPrivilegeValue( NULL,
Privilege,
&( LuidAndAttributes.Luid ) ) ) {
DebugPrintTrace(( "LookupPrivilegeValue failed, Error = %#d \n", GetLastError() ));
return( FALSE );
}
LuidAndAttributes.Attributes = SE_PRIVILEGE_ENABLED;
TokenPrivileges.PrivilegeCount = 1;
TokenPrivileges.Privileges[0] = LuidAndAttributes;
if( !AdjustTokenPrivileges( TokenHandle,
FALSE,
&TokenPrivileges,
0,
NULL,
NULL ) ) {
DebugPrintTrace(( "AdjustTokenPrivileges failed, Error = %#x \n", GetLastError() ));
return( FALSE );
}
if( GetLastError() != NO_ERROR ) {
return( FALSE );
}
return( TRUE );
}
INT __cdecl
main(
)
/*++
Routine Description:
Entry point for the ACL conversion utility.
Arguments:
None.
Return Value:
An error level--0 indicates success.
--*/
{
INT ExitCode = 0;
if( !DEFINE_CLASS_DESCRIPTOR( ACLCONV ) ||
!DEFINE_CLASS_DESCRIPTOR( SID_CACHE ) ||
!DEFINE_CLASS_DESCRIPTOR( ACL_CONVERT_NODE ) ) {
return 1;
}
{
ACLCONV Aclconv;
if( Aclconv.Initialize( &ExitCode ) ) {
if( Aclconv.IsInListMode() ) {
ExitCode = Aclconv.ListLogFile();
} else {
ExitCode = Aclconv.ConvertAcls();
}
}
}
return ExitCode;
}
DEFINE_CONSTRUCTOR( ACLCONV, PROGRAM );
ACLCONV::~ACLCONV(
)
{
Destroy();
}
VOID
ACLCONV::Construct(
)
/*++
Routine Description:
Helper method for object construction.
Arguments:
None.
Return Value:
None.
--*/
{
_DataFileRevision = DataFileRevisionUnknown;
_DataFile = NULL;
_DataFileStream = NULL;
_LogFile = NULL;
_LogFileStream = NULL;
_AclWorkFile = NULL;
_AclWorkStream = NULL;
_NewDrive = NULL;
_RootNode = NULL;
_DriveName = NULL;
_DomainName = NULL;
_SidLookupTableName = NULL;
}
VOID
ACLCONV::Destroy(
)
/*++
Routine Description:
Helper function for object destruction.
Arguments:
None.
Return Value:
None.
--*/
{
_DataFileRevision = DataFileRevisionUnknown;
_NextReadOffset = 0;
_BytesRemainingInCurrentGroup = 0;
DELETE( _DataFile );
DELETE( _DataFileStream );
DELETE( _LogFile );
DELETE( _LogFileStream );
DELETE( _AclWorkFile );
DELETE( _AclWorkStream );
DELETE( _NewDrive );
DELETE( _RootNode );
DELETE( _DriveName );
DELETE( _DomainName );
DELETE( _SidLookupTableName );
}
BOOLEAN
ACLCONV::Initialize(
OUT PINT ExitCode
)
/*++
Routine Description:
Initialize the ACLCONV object.
Arguments:
ExitCode -- Receives an error level if this method fails.
Return Value:
TRUE upon successful completion.
--*/
{
Destroy();
if( !PROGRAM::Initialize( ) ) {
Destroy();
*ExitCode = 1;
return FALSE;
}
return ParseArguments( ExitCode );
}
INT
ACLCONV::ListLogFile(
)
/*++
Routine Description:
This method reads a log file produced by a previous run of
ACLCONV and displays the errors logged to that file.
Arguments:
None.
Return Value:
An error level--zero indicates success.
--*/
{
LM_ACCESS_LIST AccessEntries[ MAX_ACCESS_ENTRIES ];
ULONG AceConversionCodes[ MAX_ACCESS_ENTRIES ];
ACLCONV_LOGFILE_HEADER LogFileHeader;
DSTRING ResourceName;
ULONG AccessEntryCount, BytesRead, ConversionCode, i;
INT ExitCode = 0;
USHORT AuditInfo;
// Open the log file and reset the seek pointer to the beginning
// of the file.
//
if( (_LogFile = SYSTEM::QueryFile( &_LogFilePath )) == NULL ||
(_LogFileStream = _LogFile->QueryStream( READ_ACCESS )) == NULL ) {
// Cannot create log file.
DisplayMessage( MSG_ACLCONV_CANT_OPEN_FILE,
ERROR_MESSAGE,
"%W",
_LogFilePath.GetPathString() );
return 1;
}
// Check the log file signature:
//
if( !_LogFileStream->MovePointerPosition( 0, STREAM_BEGINNING ) ||
!_LogFileStream->Read( (PBYTE)&LogFileHeader,
sizeof( ACLCONV_LOGFILE_HEADER ),
&BytesRead ) ||
BytesRead != sizeof( ACLCONV_LOGFILE_HEADER ) ||
LogFileHeader.Signature != AclconvLogFileSignature ) {
DisplayMessage( MSG_ACLCONV_INVALID_LOG_FILE,
ERROR_MESSAGE );
return 1;
}
_NextReadOffset = sizeof( ACLCONV_LOGFILE_HEADER );
while( ReadNextLogRecord( &ExitCode,
&ResourceName,
&ConversionCode,
&AuditInfo,
MAX_ACCESS_ENTRIES,
&AccessEntryCount,
AccessEntries,
AceConversionCodes ) ) {
// Scan to see if there are any entries to display
//
if( AccessEntryCount != 0 ) {
DisplayMessage( MSG_ACLCONV_RESOURCE_NAME,
NORMAL_MESSAGE,
"%W",
&ResourceName );
for( i = 0; i < AccessEntryCount; i++ ) {
DisplayAce( (ACL_CONVERT_CODE)ConversionCode,
(ACE_CONVERT_CODE)AceConversionCodes[i],
AccessEntries + i );
}
}
}
if( ExitCode ) {
DisplayMessage( MSG_ACLCONV_LOGFILE_READ_ERROR, ERROR_MESSAGE );
}
return ExitCode;
}
NONVIRTUAL
BOOLEAN
ACLCONV::DisplayAce(
IN ACL_CONVERT_CODE AclConvertCode,
IN ACE_CONVERT_CODE AceConvertCode,
IN PLM_ACCESS_LIST Ace
)
/*++
Routine Description:
This method displays the conversion result for a single ACE.
Arguments:
AclConvertCode -- Supplies the overall conversion code for
the resource to which this ACE is attached.
AceConvertCode -- Supplies the conversion result for this
particular ACE. Note that if the AclConvertCode
is not ACL_CONVERT_SUCCESS, it takes priority
over AceConvertCode.
Ace -- Supplies the ACE in question.
Return Value:
TRUE upon successful completion.
--*/
{
WCHAR WideNameBuffer[ UNLEN + 1 ];
DSTRING Temp;
DSTRING Name;
memset( WideNameBuffer, 0, sizeof( WideNameBuffer ) );
// Display the user's name. If it's a group, prepend an
// asterisk.
//
if( !MultiByteToWideChar( _SourceCodepage,
0,
Ace->acl_ugname,
strlen( Ace->acl_ugname ),
WideNameBuffer,
UNLEN + 1 ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
return FALSE;
}
if( !Temp.Initialize( WideNameBuffer ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
return FALSE;
}
if( Ace->acl_access & LM_ACCESS_GROUP ) {
if( !Name.Initialize( "*" ) ||
!Name.Strcat( &Temp ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
return FALSE;
}
} else {
if( !Name.Initialize( &Temp ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
return FALSE;
}
}
DisplayMessage( MSG_ACLCONV_USERNAME, NORMAL_MESSAGE, "%W", &Name );
// Display the permissions:
//
if( (Ace->acl_access & ~LM_ACCESS_GROUP) == 0 ) {
// This is a no-access ACE.
//
DisplayMessage( MSG_ACLCONV_NONE_PERM );
} else {
// This ACE grants some sort of access--check each type
// of access in turn, displaying all we find.
//
if( Ace->acl_access & LM_ACCESS_READ ) {
DisplayMessage( MSG_ACLCONV_READ_PERM );
}
if( Ace->acl_access & LM_ACCESS_WRITE ) {
DisplayMessage( MSG_ACLCONV_WRITE_PERM );
}
if( Ace->acl_access & LM_ACCESS_CREATE ) {
DisplayMessage( MSG_ACLCONV_CREATE_PERM );
}
if( Ace->acl_access & LM_ACCESS_EXEC ) {
DisplayMessage( MSG_ACLCONV_EXECUTE_PERM );
}
if( Ace->acl_access & LM_ACCESS_DELETE ) {
DisplayMessage( MSG_ACLCONV_DELETE_PERM );
}
if( Ace->acl_access & LM_ACCESS_ATRIB ) {
DisplayMessage( MSG_ACLCONV_ATTR_PERM );
}
if( Ace->acl_access & LM_ACCESS_PERM ) {
DisplayMessage( MSG_ACLCONV_PERM_PERM );
}
}
// Display the cause of failure:
//
if( AclConvertCode != ACL_CONVERT_SUCCESS ) {
// The failure is associated with the resource.
//
switch( AclConvertCode ) {
case ACL_CONVERT_RESOURCE_NOT_FOUND :
DisplayMessage( MSG_ACLCONV_FILE_NOT_FOUND );
break;
case ACL_CONVERT_ERROR :
default :
DisplayMessage( MSG_ACLCONV_ERROR_IN_CONVERSION );
break;
}
} else {
// Display the ACE conversion result.
//
switch( AceConvertCode ) {
case ACL_CONVERT_SUCCESS :
DisplayMessage( MSG_ACLCONV_ACE_CONVERTED );
break;
case ACE_CONVERT_DROPPED :
DisplayMessage( MSG_ACLCONV_ACE_DROPPED );
break;
case ACE_CONVERT_SID_NOT_FOUND :
DisplayMessage( MSG_ACLCONV_SID_NOT_FOUND );
break;
case ACE_CONVERT_ERROR :
default:
DisplayMessage( MSG_ACLCONV_ERROR_IN_CONVERSION );
break;
}
}
return TRUE;
}
NONVIRTUAL
BOOLEAN
ACLCONV::ReadNextLogRecord(
OUT PINT ExitCode,
OUT PWSTRING ResourceString,
OUT PULONG ConversionCode,
OUT PUSHORT AuditInfo,
IN ULONG MaxEntries,
OUT PULONG AccessEntryCount,
OUT PLM_ACCESS_LIST AccessEntries,
OUT PULONG AceConversionCodes
)
/*++
Routine Description:
This method reads the next log entry from the log file.
Arguments:
ExitCode -- receives an exit code if an error occurs.
ResourceString -- receives the name of the resource
ConversionCode -- receives the conversion result for this resource.
AuditInfo -- receives the audit information for this resource.
MaxEntries -- supplies the maximum number of ACE's that can
be written to the output buffers.
AccessEntryCount -- receives the number of ACE's written to
the output buffers.
AccessEntries -- receives the logged ACE's
AceConversionCodes -- receives the conversion results for the
individual ACE's.
Return Value:
TRUE upon successful completion (in which case ExitCode may
be ignored). FALSE if there are no more entries (in which
case ExitCode is zero) or if an error occurs (in which case
ExitCode is non-zero).
--*/
{
ACLCONV_LOG_RECORD_HEADER Header;
ULONG BytesRead;
if( _LogFileStream->IsAtEnd() ) {
// No more entries to read.
*ExitCode = 0;
return FALSE;
}
// Read the log record header
//
if( !_LogFileStream->Read( (PBYTE)&Header,
sizeof( ACLCONV_LOG_RECORD_HEADER ),
&BytesRead ) ||
BytesRead != sizeof( ACLCONV_LOG_RECORD_HEADER ) ) {
*ExitCode = 1;
return FALSE;
}
*ConversionCode = Header.ConversionResult;
*AuditInfo = Header.LmAuditMask;
*AccessEntryCount = Header.AccessEntryCount;
// Make sure that the name is not longer than the maximum
// name length (plus room for trailing NULL) and then read
// it into the name workspace and use it to initialize the
// client's resource name string.
//
if( Header.ResourceNameLength > MAX_RESOURCE_NAME_LENGTH + 1 ||
!_LogFileStream->Read( (PBYTE)_NameBuffer,
Header.ResourceNameLength * sizeof( WCHAR ),
&BytesRead ) ||
BytesRead != Header.ResourceNameLength * sizeof( WCHAR ) ||
!ResourceString->Initialize( _NameBuffer ) ) {
*ExitCode = 1;
return FALSE;
}
// Make sure the ACE's and their associated convert codes will
// fit in the supplied buffers:
//
if( Header.AccessEntryCount > MaxEntries ) {
*ExitCode = 1;
return FALSE;
}
// Read the ACE conversion codes and the ACE's themselves:
//
if( Header.AccessEntryCount != 0 &&
( !_LogFileStream->Read( (PBYTE)AceConversionCodes,
Header.AccessEntryCount * sizeof( ULONG ),
&BytesRead ) ||
BytesRead != Header.AccessEntryCount * sizeof( ULONG ) ) ||
( !_LogFileStream->Read( (PBYTE)AccessEntries,
Header.AccessEntryCount *
sizeof( LM_ACCESS_LIST ),
&BytesRead ) ||
BytesRead != Header.AccessEntryCount * sizeof( LM_ACCESS_LIST ) ) ) {
*ExitCode = 1;
return FALSE;
}
return TRUE;
}
INT
ACLCONV::ConvertAcls(
)
/*++
Routine Description:
This method reads the ACL's from the data file into a tree
of ACL Convert Nodes, and then converts the ACL's to NT
security descriptors and applies them to the files and
directories in question.
Arguments:
None.
Return Value:
An error level--zero indicates success.
--*/
{
LM_ACCESS_LIST AccessEntries[MAX_ACCESS_ENTRIES];
INHERITANCE_BUFFER Inheritance;
FSTRING NtfsString;
DSTRING FsName;
DSTRING CurrentResource;
PATH CurrentResourcePath;
ACLCONV_LOGFILE_HEADER LogfileHeader;
PARRAY Components = NULL;
PARRAY_ITERATOR ComponentIterator = NULL;
PACL_CONVERT_NODE CurrentNode, NextNode;
PWSTRING CurrentComponent;
ULONG AccessEntryCount, BytesWritten;
USHORT LmAuditInfo;
INT ExitCode = 0;
DSTRING AclWorkString;
// Open aclwork.dat and read the contents into a special
// sid cache.
if (!AclWorkString.Initialize(L"aclwork.dat")) {
return 1;
}
if (!_AclWorkPath.Initialize(&AclWorkString)) {
return 1;
}
if (NULL == (_AclWorkFile = SYSTEM::QueryFile(&_AclWorkPath))) {
// try to open aclwork.dat in the same directory as the
// data file.
if (!_AclWorkPath.Initialize(&_DataFilePath)) {
return 1;
}
if (!_AclWorkPath.SetName(&AclWorkString)) {
return 1;
}
_AclWorkFile = SYSTEM::QueryFile(&_AclWorkPath);
}
if (NULL != _AclWorkFile &&
NULL != (_AclWorkStream = _AclWorkFile->QueryStream(READ_ACCESS))) {
// DisplayMessage( MSG_ACLCONV_USING_ACLWORK, NORMAL_MESSAGE );
if (!ReadAclWorkSids()) {
return 1;
}
}
// Open the data file and determine its format (ie. what
// revision of BackAcc produced it).
if( (_DataFile = SYSTEM::QueryFile( &_DataFilePath )) == NULL ||
(_DataFileStream = _DataFile->QueryStream( READ_ACCESS )) == NULL ) {
DisplayMessage( MSG_ACLCONV_CANT_OPEN_FILE,
ERROR_MESSAGE,
"%W",
_DataFilePath.GetPathString() );
return 1;
}
// Note that DetermineDataFileRevision sets _DataFileRevision.
if( !DetermineDataFileRevision( ) ||
_DataFileRevision == DataFileRevisionUnknown ) {
DisplayMessage( MSG_ACLCONV_DATAFILE_BAD_FORMAT,
ERROR_MESSAGE,
"%W",
_DataFilePath.GetPathString() );
return 1;
}
// Create the log file.
LogfileHeader.Signature = AclconvLogFileSignature;
if( (_LogFile = SYSTEM::MakeFile( &_LogFilePath )) == NULL ||
(_LogFileStream = _LogFile->QueryStream( WRITE_ACCESS )) == NULL ||
!_LogFileStream->Write( (PBYTE)&LogfileHeader,
sizeof( ACLCONV_LOGFILE_HEADER ),
&BytesWritten ) ||
BytesWritten != sizeof( ACLCONV_LOGFILE_HEADER ) ) {
// Cannot create log file.
DisplayMessage( MSG_ACLCONV_CANT_OPEN_FILE,
ERROR_MESSAGE,
"%W",
_LogFilePath.GetPathString() );
return 1;
}
while( ReadNextAcl( &ExitCode,
&CurrentResource,
MAX_ACCESS_ENTRIES,
&AccessEntryCount,
(PVOID)AccessEntries,
&LmAuditInfo ) ) {
if( CurrentResource.QueryChCount() == 0 ) {
// This resource has no name; ignore it.
//
continue;
}
if( !CurrentResourcePath.Initialize( &CurrentResource ) ) {
DisplayMessage( MSG_ACLCONV_DATAFILE_ERROR, ERROR_MESSAGE );
return 1;
}
// If the user specified a substitute drive, use it.
//
if( _NewDrive != NULL &&
!CurrentResourcePath.SetDevice( _NewDrive ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
return 1;
}
if( _RootNode == NULL ) {
// This is the first ACL--create the root of the tree
// and determine the name of the drive.
if( !(_DriveName = CurrentResourcePath.QueryRoot()) ||
!(_RootNode = NEW ACL_CONVERT_NODE) ||
!_RootNode->Initialize( _DriveName ) ) {
DELETE( _RootNode );
DELETE( _DriveName );
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
return 1;
}
}
// Fetch the component array for this resource.
DELETE( ComponentIterator );
if( Components != NULL ) {
Components->DeleteAllMembers();
}
DELETE( Components );
if( !(Components = CurrentResourcePath.QueryComponentArray()) ||
!(ComponentIterator = (PARRAY_ITERATOR)
Components->QueryIterator()) ) {
DELETE( ComponentIterator );
if( Components != NULL ) {
Components->DeleteAllMembers();
}
DELETE( Components );
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
return 1;
}
CurrentNode = _RootNode;
ComponentIterator->Reset();
// The first component is the drive & root directory, which
// isn't interesting.
CurrentComponent = (PWSTRING)ComponentIterator->GetNext();
// Traverse the tree down to the end of the path, creating
// new nodes as needed.
while( (CurrentComponent = (PWSTRING)ComponentIterator->GetNext())
!= NULL ) {
if( !(NextNode = CurrentNode->GetChild( CurrentComponent )) &&
!(NextNode = CurrentNode->AddChild( CurrentComponent )) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
return 1;
}
CurrentNode = NextNode;
}
// Add the Lanman ACL to the node which represents the end of
// the path.
if( !CurrentNode->AddLanmanAcl( AccessEntryCount,
AccessEntries,
LmAuditInfo ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
return 1;
}
}
if( ExitCode != 0 ) {
DisplayMessage( MSG_ACLCONV_DATAFILE_ERROR, ERROR_MESSAGE );
return 1;
}
// Traverse the tree and convert all the ACE's, propagating
// as we go.
//
// Adjust this process' privileges so that it can twiddle
// System ACL's.
//
if( !EnablePrivilege( (LPWSTR)SE_SECURITY_NAME ) ) {
DisplayMessage( MSG_ACLCONV_CONVERSION_ERROR, ERROR_MESSAGE );
ExitCode = 1;
}
if( ExitCode == 0 &&
_RootNode != NULL ) {
// Make sure the target drive is NTFS.
//
if( !NtfsString.Initialize( (PWSTR)L"NTFS" ) ||
!QueryFileSystemName( _RootNode->GetName(), &FsName ) ) {
DisplayMessage( MSG_ACLCONV_CANT_DETERMINE_FILESYSTEM, ERROR_MESSAGE );
ExitCode = 1;
} else if( FsName.Stricmp( &NtfsString ) != 0 ) {
DisplayMessage( MSG_ACLCONV_TARGET_NOT_NTFS, ERROR_MESSAGE );
ExitCode = 1;
} else {
// Set up an empty inheritance buffer to pass
// to the root.
//
Inheritance.RecessiveDeniedAces = NULL;
Inheritance.RecessiveAllowedAces = NULL;
Inheritance.DominantDeniedAces = NULL;
Inheritance.DominantAllowedAces = NULL;
Inheritance.RecessiveDeniedMaxLength = 0;
Inheritance.RecessiveAllowedMaxLength = 0;
Inheritance.DominantDeniedMaxLength = 0;
Inheritance.DominantAllowedMaxLength = 0;
Inheritance.RecessiveDeniedLength = 0;
Inheritance.RecessiveAllowedLength = 0;
Inheritance.DominantDeniedLength = 0;
Inheritance.DominantAllowedLength = 0;
if( !_RootNode->Convert( NULL,
&Inheritance,
this ) ) {
DisplayMessage( MSG_ACLCONV_CONVERSION_ERROR, ERROR_MESSAGE );
ExitCode = 1;
}
}
}
if( ExitCode == 0 ) {
DisplayMessage( MSG_ACLCONV_CONVERT_COMPLETE );
}
return ExitCode;
}
BOOLEAN
ACLCONV::LogConversion(
IN PPATH Resource,
IN ULONG ConversionCode,
IN ULONG LmAuditInfo,
IN ULONG AccessEntryCount,
IN PCULONG AceConversionCodes,
IN PCLM_ACCESS_LIST AccessEntries
)
/*+
Routine Description:
This method writes information about the conversion of a resource
to the log file.
Arguments:
Resource -- Supplies the path to the resource
ConversionCode -- Supplies the conversion result for the
resource.
LmAuditInfo -- Supplies the Lanman 2.x audit information
associated with the resource.
AccessEntryCount -- Supplies the number of Lanman 2.x access
entries associated with the resource.
AceConversionCodes -- Supplies the conversion results of the
individual ACE's
AccessEntries -- Supplies the Lanman 2.x access control
entries.
Return Value:
TRUE upon successful completion.
--*/
{
ACLCONV_LOG_RECORD_HEADER Header;
PCWSTRING PathString;
ULONG NameLength, BytesWritten;
DebugPtrAssert( Resource );
DebugPtrAssert( _LogFileStream );
if( (PathString = Resource->GetPathString()) == NULL ||
(NameLength = PathString->QueryChCount()) > MAX_RESOURCE_NAME_LENGTH ||
!PathString->QueryWSTR( 0,
TO_END,
_NameBuffer,
MAX_RESOURCE_NAME_LENGTH + 1 ) ){
return FALSE;
}
Header.ResourceNameLength = NameLength + 1;
Header.ConversionResult = ConversionCode;
Header.LmAuditMask = (USHORT)LmAuditInfo;
Header.AccessEntryCount = (USHORT)AccessEntryCount;
if(!_LogFileStream->Write( (PBYTE)&Header,
sizeof( ACLCONV_LOG_RECORD_HEADER ),
&BytesWritten ) ||
BytesWritten != sizeof( ACLCONV_LOG_RECORD_HEADER ) ||
!_LogFileStream->Write( (PBYTE)_NameBuffer,
(NameLength + 1) * sizeof( WCHAR ),
&BytesWritten ) ||
BytesWritten != (NameLength + 1) * sizeof( WCHAR )) {
DisplayMessage( MSG_ACLCONV_LOGFILE_ERROR, ERROR_MESSAGE );
return FALSE;
}
if( AccessEntryCount != 0 &&
( !_LogFileStream->Write( (PBYTE)AceConversionCodes,
AccessEntryCount * sizeof(ULONG),
&BytesWritten ) ||
BytesWritten != AccessEntryCount * sizeof(ULONG) ||
!_LogFileStream->Write( (PBYTE)AccessEntries,
AccessEntryCount * sizeof( LM_ACCESS_LIST ),
&BytesWritten ) ||
BytesWritten != AccessEntryCount * sizeof( LM_ACCESS_LIST ) ) ) {
DisplayMessage( MSG_ACLCONV_LOGFILE_ERROR, ERROR_MESSAGE );
return FALSE;
}
return TRUE;
}
BOOLEAN
ACLCONV::ParseArguments(
OUT PINT ExitCode
)
/*++
Routine Description:
This method parses the arguments given to ACLCONV and sets the
state of the object appropriately.
The accepted syntax is:
ACLCONV [/?] [/V] /DATA:datafile /LOG:logfile
Arguments:
ExitCode -- Receives an exit code if the method fails.
Return Value:
TRUE upon successful completion.
Note that this method will fail, but return an exit-code
of zero (success) if the user specifies the /? argument.
--*/
{
ARRAY ArgArray; // Array of arguments
ARRAY LexArray; // Array of lexemes
ARGUMENT_LEXEMIZER ArgLex; // Argument Lexemizer
STRING_ARGUMENT ProgramNameArgument; // Program name argument
PATH_ARGUMENT DataFileArgument; // Path to data file
PATH_ARGUMENT LogFileArgument; // Path to log file
PATH_ARGUMENT DriveArgument; // New drive to use
FLAG_ARGUMENT ListArgument; // List flag argument
FLAG_ARGUMENT HelpArgument; // Help flag argument
STRING_ARGUMENT DomainArgument; // Domain name argument
LONG_ARGUMENT CodepageArgument; // Source Codepage argument
STRING_ARGUMENT SidLookupArgument; // Filename of lookup table
PWSTRING InvalidArg; // Invalid argument catcher
DSTRING Backslash; // Backslash
DSTRING RootDir; // Root directory of the new drive
UINT DriveType;
DebugPtrAssert( ExitCode );
//
// Initialize all the argument parsing machinery.
//
if( !ArgArray.Initialize( 5, 1 ) ||
!LexArray.Initialize( 5, 1 ) ||
!ArgLex.Initialize( &LexArray ) ||
!HelpArgument.Initialize( "/?" ) ||
!ListArgument.Initialize( "/LIST" ) ||
!ProgramNameArgument.Initialize( "*" ) ||
!DataFileArgument.Initialize( "/DATA:*" ) ||
!LogFileArgument.Initialize( "/LOG:*" ) ||
!DriveArgument.Initialize( "/NEWDRIVE:*" ) ||
!DomainArgument.Initialize( "/DOMAIN:*" ) ||
!CodepageArgument.Initialize( "/CODEPAGE:*" ) ||
!SidLookupArgument.Initialize( "/SIDLOOKUP:*" ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
*ExitCode = 1;
return FALSE;
}
//
// The ACL conversion utility is case-insensitive
//
ArgLex.SetCaseSensitive( FALSE );
// Put the arguments into the argument array
if( !ArgArray.Put( &HelpArgument ) ||
!ArgArray.Put( &ListArgument ) ||
!ArgArray.Put( &DataFileArgument ) ||
!ArgArray.Put( &LogFileArgument ) ||
!ArgArray.Put( &DriveArgument ) ||
!ArgArray.Put( &DomainArgument ) ||
!ArgArray.Put( &CodepageArgument ) ||
!ArgArray.Put( &ProgramNameArgument ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
*ExitCode = 1;
return FALSE;
}
//
// Lexemize the command line.
//
if ( !ArgLex.PrepareToParse() ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
*ExitCode = 1;
return FALSE;
}
//
// Parse the arguments.
//
if( !ArgLex.DoParsing( &ArgArray ) ) {
DisplayMessage( MSG_CONV_INVALID_PARAMETER, ERROR_MESSAGE, "%W", InvalidArg = ArgLex.QueryInvalidArgument() );
DELETE( InvalidArg );
*ExitCode = 1;
return FALSE;
}
//
// If the user requested help, give it.
//
if( HelpArgument.QueryFlag() ) {
DisplayMessage( MSG_ACLCONV_USAGE );
*ExitCode = 0;
return FALSE;
}
// The log file must be specified, and either the data
// file or the list argument (but not both) must be
// provided.
//
if( !LogFileArgument.IsValueSet() ||
( !DataFileArgument.IsValueSet() &&
!ListArgument.IsValueSet() ) ||
( DataFileArgument.IsValueSet() &&
ListArgument.IsValueSet() ) ) {
DisplayMessage( MSG_ACLCONV_USAGE );
*ExitCode = 1;
return FALSE;
}
// If the drive argument has been supplied, record it:
//
if( DriveArgument.IsValueSet() ) {
_NewDrive = DriveArgument.GetPath()->QueryDevice();
if( _NewDrive == NULL ) {
DisplayMessage( MSG_INVALID_PARAMETER,
ERROR_MESSAGE,
"%W",
DriveArgument.GetPath()->GetPathString() );
*ExitCode = 1;
return FALSE;
}
// Validate the drive.
//
if( !RootDir.Initialize( _NewDrive ) ||
!Backslash.Initialize( "\\" ) ||
!RootDir.Strcat( &Backslash ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
*ExitCode = 1;
return FALSE;
}
DriveType = GetDriveTypeW( RootDir.GetWSTR() );
switch ( DriveType ) {
case DRIVE_FIXED:
case DRIVE_REMOVABLE:
// The drive type is acceptable.
//
break;
case 0:
case 1:
case DRIVE_CDROM:
case DRIVE_REMOTE:
case DRIVE_RAMDISK:
default:
// The drive type is invalid.
//
DisplayMessage( MSG_ACLCONV_INVALID_DRIVE, ERROR_MESSAGE, "%W", _NewDrive );
*ExitCode = 1;
return FALSE;
}
}
// If a domain name has been specified, remember it:
//
if( DomainArgument.IsValueSet() ) {
if( (_DomainName = NEW DSTRING) == NULL ||
!_DomainName->Initialize( DomainArgument.GetString() ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
*ExitCode = 1;
return FALSE;
}
}
// If a source codepage has been specified, use it; otherwise,
// use CP_OEMCP as default.
//
if( !CodepageArgument.IsValueSet() ) {
_SourceCodepage = CP_OEMCP;
} else {
_SourceCodepage = CodepageArgument.QueryLong();
if( !IsValidCodePage( _SourceCodepage ) ) {
DisplayMessage( MSG_ACLCONV_BAD_CODEPAGE, ERROR_MESSAGE );
*ExitCode = 1;
return FALSE;
}
}
if( SidLookupArgument.IsValueSet() ) {
_SidLookupTableName = SidLookupArgument.GetString()->QueryWSTR();
}
_IsInListMode = ListArgument.IsValueSet();
if( _IsInListMode ) {
// In list mode, only the Log File path need be present.
//
if( !_LogFilePath.Initialize( LogFileArgument.GetPath() ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
*ExitCode = 1;
return FALSE;
}
} else {
// The object is not in list mode, so both the Data File
// and Log File paths must be present.
//
if( !_DataFilePath.Initialize( DataFileArgument.GetPath() ) ||
!_LogFilePath.Initialize( LogFileArgument.GetPath() ) ) {
DisplayMessage( MSG_CONV_NO_MEMORY, ERROR_MESSAGE );
*ExitCode = 1;
return FALSE;
}
}
if( !_SidCache.Initialize( 100 ) ) {
return FALSE;
}
if (!_AclWorkSids.Initialize(1)) {
return FALSE;
}
return TRUE;
}
BOOLEAN
ACLCONV::DetermineDataFileRevision(
)
/*++
Routine Description:
This method examines the data file to determine what revision
of the Lanman BackAcc utility produced it. It sets the private
member variable _DataFileRevision.
Arguments:
None.
Return Value:
TRUE upon successful completion. _DataFileRevision is set
appropriately.
--*/
{
CHAR Buffer[RecognitionSize];
ULONG BytesRead;
// If the first four bytes of the file are the LM2.0 backacc signature,
// assume the data file was produced by LM2.0 backacc. LM2.1 backacc
// data files are recognizable by the string "LM210 BACKACC" at
// byte offset 4.
_DataFileRevision = DataFileRevisionUnknown;
if( _DataFileStream == NULL ||
!_DataFileStream->ReadAt( (PBYTE)Buffer,
RecognitionSize,
0,
STREAM_BEGINNING,
&BytesRead ) ||
BytesRead != RecognitionSize ) {
return FALSE;
}
if( strncmp( Buffer, (PCHAR)&Lm20BackaccSignature, 4) == 0 ) {
_DataFileRevision = DataFileRevisionLanman20;
return TRUE;
}
if( strncmp( Buffer+Lm21BackaccSignatureOffset,
Lm21BackaccSignature,
Lm21BackaccSignatureLength ) == 0 ) {
_DataFileRevision = DataFileRevisionLanman21;
return TRUE;
}
return FALSE;
}
BOOLEAN
ACLCONV::UpdateAndQueryCurrentLM21Name(
IN ULONG DirectoryLevel,
IN PCSTR NewComponent,
OUT PWSTRING CurrentName
)
/*++
Routine Description:
This function updates the current resource name while
traversing the LM21 BackAcc data file.
Arguments:
DirectoryLevel -- Supplies the directory level of the
most-recently-encountered component.
(0 is the drive, 1 is the root directory,
2 is an element in the root directory, and
so forth.)
NewComponent -- Supplies the name of the most-recently-
encountered component.
CurrentName -- Receives the updated current resource name.
Return Value:
TRUE upon successful completion.
--*/
{
STATIC PDSTRING Components[256];
STATIC PDSTRING BackSlash;
STATIC ULONG CurrentLevel = 0;
WCHAR ComponentBuffer[ MAX_RESOURCE_NAME_LENGTH ];
ULONG i;
// If BackSlash hasn't been initialized yet,
// initialize it.
//
if( BackSlash == NULL ) {
if( (BackSlash = NEW DSTRING) == NULL ||
!BackSlash->Initialize( "\\" ) ) {
return FALSE;
}
}
if( DirectoryLevel == 0 ) {
return( CurrentName->Initialize( "" ) );
}
while( CurrentLevel >= DirectoryLevel ) {
// Trim off the last component of the path.
//
CurrentLevel--;
DELETE( Components[CurrentLevel] );
}
// Now we're ready to add a new component to the end of the
// current path.
//
if( DirectoryLevel != CurrentLevel + 1 ) {
DebugPrint( "ACLCONV: skipped a level in name tree.\n" );
return FALSE;
}
memset( ComponentBuffer, 0, sizeof( ComponentBuffer ) );
if( (Components[CurrentLevel] = NEW DSTRING) == NULL ||
!MultiByteToWideChar( _SourceCodepage,
0,
NewComponent,
strlen( NewComponent ),
ComponentBuffer,
MAX_RESOURCE_NAME_LENGTH ) ||
!Components[CurrentLevel]->Initialize( ComponentBuffer ) ) {
return FALSE;
}
CurrentLevel++;
// Now copy the current path to the output string.
//
if( !CurrentName->Initialize( "" ) ) {
return FALSE;
}
for( i = 0; i < CurrentLevel; i++ ) {
// There's no backslash before the first component (the drive);
// if the prefix ends in a backslash, don't add one. Otherwise,
// add a backslash.
//
if( i > 0 &&
CurrentName->QueryChAt( CurrentName->QueryChCount() - 1 ) != '\\' &&
!CurrentName->Strcat( BackSlash ) ) {
return FALSE;
}
// Add the next component
//
if( !CurrentName->Strcat( Components[i] ) ) {
return FALSE;
}
}
return TRUE;
}
BOOLEAN
ACLCONV::ReadNextAcl(
OUT PINT ExitCode,
OUT PWSTRING ResourceString,
IN ULONG MaxEntries,
OUT PULONG AccessEntryCount,
OUT PVOID AccessEntries,
OUT PUSHORT AuditInfo
)
/*++
Routine Description:
This method reads the next ACL record from the data file. The
ACL record describes the resource and its associated Access Control
Entries.
Arguments:
ExitCode -- Receives an error level if an error occurs.
ResourceString -- Receives the name of the resource.
MaxEntries -- Supplies the maximum number of entries
that will fit in the supplied buffer.
AccessEntryCount -- Receives the number of access entries read.
AccessEntries -- Receives the access entries.
AuditInfo -- Receives the Lanman 2.x audit information
for the resource.
Return Value:
TRUE upon successful completion. If the end of the file is
reached without error, this method returns FALSE, with *ExitCode
equal to zero.
--*/
{
CHAR ResourceName[ MAX_RESOURCE_NAME_LENGTH ];
WCHAR WideResourceName[ MAX_RESOURCE_NAME_LENGTH ];
lm20_resource_info ResourceInfo;
lm21_aclhdr Lm21Header;
lm21_aclrec Lm21AclRec;
ULONG BytesRead, TotalBytesRead;
if( _DataFileStream == NULL ) {
DebugAbort( "Data stream not set up.\n" );
return FALSE;
}
switch( _DataFileRevision ) {
case DataFileRevisionUnknown :
DebugAbort( "Trying to read from unknown data file revision.\n" );
*ExitCode = 1;
return FALSE;
case DataFileRevisionLanman20 :
if( _NextReadOffset == 0 ) {
// This is the first read, so we skip over the header
// information.
_NextReadOffset = LM20_BACKACC_HEADER_SIZE +
LM20_INDEX_SIZE * LM20_NINDEX;
}
if( !_DataFileStream->MovePointerPosition( _NextReadOffset,
STREAM_BEGINNING ) ) {
*ExitCode = 1;
return FALSE;
}
if( _DataFileStream->IsAtEnd() ) {
// No more entries to read.
*ExitCode = 0;
return FALSE;
}
// Read the resource header information.
if( !_DataFileStream->Read( (PBYTE)&ResourceInfo,
LM20_RESOURCE_INFO_HEADER_SIZE,
&BytesRead ) ||
BytesRead != LM20_RESOURCE_INFO_HEADER_SIZE ) {
*ExitCode = 1;
return FALSE;
}
// Read the name and initialize ResourceString
//
memset( WideResourceName, 0, sizeof( WideResourceName ) );
if( ResourceInfo.namelen > MAX_RESOURCE_NAME_LENGTH ||
!_DataFileStream->Read( (PBYTE)ResourceName,
ResourceInfo.namelen,
&BytesRead ) ||
BytesRead != ResourceInfo.namelen ||
!MultiByteToWideChar( _SourceCodepage,
0,
ResourceName,
strlen( ResourceName ),
WideResourceName,
MAX_RESOURCE_NAME_LENGTH ) ||
!ResourceString->Initialize( WideResourceName ) ) {
*ExitCode = 1;
return FALSE;
}
// Read the access entries
if( (ULONG)ResourceInfo.acc1_count > MaxEntries ||
!_DataFileStream->Read( (PBYTE)AccessEntries,
ResourceInfo.acc1_count *
LM_ACCESS_LIST_SIZE,
&BytesRead ) ||
BytesRead != (ULONG)( ResourceInfo.acc1_count * LM_ACCESS_LIST_SIZE ) ) {
*ExitCode = 1;
return FALSE;
}
*AccessEntryCount = ResourceInfo.acc1_count;
*AuditInfo = ResourceInfo.acc1_attr;
_NextReadOffset += LM20_RESOURCE_INFO_HEADER_SIZE +
ResourceInfo.namelen +
ResourceInfo.acc1_count * LM_ACCESS_LIST_SIZE;
return TRUE;
case DataFileRevisionLanman21 :
while( _NextReadOffset == 0 || _BytesRemainingInCurrentGroup == 0 ) {
// The current offset is at the beginning of a group. If
// this also the end of the file, there are no more records
// to read; otherwise, read the header of this group.
if( !_DataFileStream->MovePointerPosition( _NextReadOffset,
STREAM_BEGINNING ) ) {
*ExitCode = 1;
return FALSE;
}
if( _DataFileStream->IsAtEnd() ) {
// No more entries to read.
*ExitCode = 0;
return FALSE;
}
if( !_DataFileStream->Read( (PBYTE)&Lm21Header,
LM21_ACLHDR_SIZE,
&BytesRead ) ||
BytesRead != LM21_ACLHDR_SIZE ) {
*ExitCode = 1;
return FALSE;
}
_NextReadOffset += LM21_ACLHDR_SIZE;
_BytesRemainingInCurrentGroup = Lm21Header.NxtHdr -
_NextReadOffset;
}
// Read the ACL Record
if( !_DataFileStream->Read( (PBYTE)&Lm21AclRec,
LM21_ACLREC_SIZE,
&BytesRead ) ||
BytesRead != LM21_ACLREC_SIZE ) {
*ExitCode = 1;
return FALSE;
}
TotalBytesRead = BytesRead;
// Read the name and initialize ResourceString
if( Lm21AclRec.NameBytes > MAX_RESOURCE_NAME_LENGTH ||
!_DataFileStream->Read( (PBYTE)ResourceName,
Lm21AclRec.NameBytes,
&BytesRead ) ||
BytesRead != (ULONG)Lm21AclRec.NameBytes ) {
*ExitCode = 1;
return FALSE;
}
if( !UpdateAndQueryCurrentLM21Name( Lm21AclRec.DirLvl,
ResourceName,
ResourceString ) ) {
*ExitCode = 1;
return FALSE;
}
TotalBytesRead += BytesRead;
// Read the access entries:
//
if( Lm21AclRec.AclCnt == -1 ) {
*AccessEntryCount = 0;
*AuditInfo = 0;
} else {
if( (ULONG)Lm21AclRec.AclCnt > MaxEntries ||
!_DataFileStream->Read( (PBYTE)AccessEntries,
Lm21AclRec.AclCnt *
LM_ACCESS_LIST_SIZE,
&BytesRead ) ||
BytesRead != (ULONG)( Lm21AclRec.AclCnt * LM_ACCESS_LIST_SIZE ) ) {
*ExitCode = 1;
return FALSE;
}
TotalBytesRead += BytesRead;
*AccessEntryCount = Lm21AclRec.AclCnt;
*AuditInfo = Lm21AclRec.AuditAttrib;
}
// Check that this read didn't overflow the current group--if
// it did, the file format is not as expected.
if( TotalBytesRead > _BytesRemainingInCurrentGroup ) {
*ExitCode = 1;
return FALSE;
}
_NextReadOffset += TotalBytesRead;
_BytesRemainingInCurrentGroup -= TotalBytesRead;
return TRUE;
default:
*ExitCode = 1;
return FALSE;
}
}
BOOLEAN
ACLCONV::ReadAclWorkSids(
)
{
WORD n_entries;
ULONG n_read;
ULONG i;
USHORT name_len;
WCHAR name[64];
DSTRING Name;
PSID pSid;
ULONG sid_len;
DSTRING Domain;
if (!Domain.Initialize("UserConv")) {
DisplayMessage(MSG_CONV_NO_MEMORY, ERROR_MESSAGE);
return FALSE;
}
if (!_AclWorkStream->Read((PUCHAR)&n_entries, sizeof(WORD), &n_read) ||
n_read != sizeof(WORD)) {
DisplayMessage(MSG_ACLCONV_DATAFILE_BAD_FORMAT, ERROR_MESSAGE,
"%W", _AclWorkPath.GetPathString());
return FALSE;
}
if (!_AclWorkSids.Initialize(n_entries)) {
DisplayMessage(MSG_CONV_NO_MEMORY, ERROR_MESSAGE);
return FALSE;
}
//
// Read each entry from the aclwork.dat file and add it to this
// sid cache.
//
for (i = 0; i < n_entries; ++i) {
// read the length of the name
if (!_AclWorkStream->Read((PUCHAR)&name_len, sizeof(name_len),
&n_read) || n_read != sizeof(name_len)) {
DisplayMessage(MSG_ACLCONV_DATAFILE_BAD_FORMAT, ERROR_MESSAGE,
"%W", _AclWorkPath.GetPathString());
return FALSE;
}
// read the name
if (!_AclWorkStream->Read((PUCHAR)name, name_len, &n_read) ||
n_read != name_len) {
DisplayMessage(MSG_ACLCONV_DATAFILE_BAD_FORMAT, ERROR_MESSAGE,
"%W", _AclWorkPath.GetPathString());
return FALSE;
}
name[name_len / sizeof(WCHAR)] = UNICODE_NULL;
if (!Name.Initialize(name)) {
DisplayMessage(MSG_CONV_NO_MEMORY, ERROR_MESSAGE);
return FALSE;
}
if (!_AclWorkStream->Read((PUCHAR)&sid_len, sizeof(sid_len), &n_read) ||
n_read != sizeof(sid_len)) {
DisplayMessage(MSG_ACLCONV_DATAFILE_BAD_FORMAT, ERROR_MESSAGE,
"%W", _AclWorkPath.GetPathString());
return FALSE;
}
pSid = (PSID)MALLOC(sid_len);
if (NULL == pSid) {
DisplayMessage(MSG_CONV_NO_MEMORY, ERROR_MESSAGE);
return FALSE;
}
if (!_AclWorkStream->Read((PUCHAR)pSid, sid_len, &n_read) ||
n_read != sid_len) {
DisplayMessage(MSG_ACLCONV_DATAFILE_BAD_FORMAT, ERROR_MESSAGE,
"%W", _AclWorkPath.GetPathString());
return FALSE;
}
DebugAssert(RtlValidSid(pSid));
if (!_AclWorkSids.CacheSid( &Domain, &Name, pSid, sid_len)) {
DisplayMessage(MSG_CONV_NO_MEMORY, ERROR_MESSAGE);
return FALSE;
}
FREE(pSid);
}
return TRUE;
}