627 lines
15 KiB
C
627 lines
15 KiB
C
|
/*++
|
|||
|
|
|||
|
Copyright (c) 1989 Microsoft Corporation
|
|||
|
|
|||
|
Module Name:
|
|||
|
|
|||
|
share.c
|
|||
|
|
|||
|
Abstract:
|
|||
|
|
|||
|
This module contains routines for adding, deleting, and enumerating
|
|||
|
shared resources.
|
|||
|
|
|||
|
Author:
|
|||
|
|
|||
|
David Treadwell (davidtr) 15-Nov-1989
|
|||
|
|
|||
|
Revision History:
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
#include "precomp.h"
|
|||
|
#include "share.tmh"
|
|||
|
#pragma hdrstop
|
|||
|
|
|||
|
#ifdef ALLOC_PRAGMA
|
|||
|
#pragma alloc_text( PAGE, SrvVerifyShare )
|
|||
|
#pragma alloc_text( PAGE, SrvFindShare )
|
|||
|
#pragma alloc_text( PAGE, SrvRemoveShare )
|
|||
|
#pragma alloc_text( PAGE, SrvAddShare )
|
|||
|
#pragma alloc_text( PAGE, SrvShareEnumApiHandler )
|
|||
|
#endif
|
|||
|
|
|||
|
|
|||
|
PSHARE
|
|||
|
SrvVerifyShare (
|
|||
|
IN PWORK_CONTEXT WorkContext,
|
|||
|
IN PSZ ShareName,
|
|||
|
IN PSZ ShareTypeString,
|
|||
|
IN BOOLEAN ShareNameIsUnicode,
|
|||
|
IN BOOLEAN IsNullSession,
|
|||
|
OUT PNTSTATUS Status,
|
|||
|
OUT PUNICODE_STRING ServerName OPTIONAL
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Attempts to find a share that matches a given name and share type.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ShareName - name of share to verify, including the server name.
|
|||
|
(I.e., of the form "\\server\share", as received in the SMB.)
|
|||
|
|
|||
|
ShareTypeString - type of the share (A:, LPT1:, COMM, IPC, or ?????).
|
|||
|
|
|||
|
ShareNameIsUnicode - if TRUE, the share name is Unicode.
|
|||
|
|
|||
|
IsNullSession - Is this the NULL session?
|
|||
|
|
|||
|
Status - Reason why this call failed. Not used if a share is returned.
|
|||
|
|
|||
|
ServerName - The servername part of the requested resource.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
A pointer to a share matching the given name and share type, or NULL
|
|||
|
if none exists.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PSHARE share;
|
|||
|
BOOLEAN anyShareType = FALSE;
|
|||
|
SHARE_TYPE shareType;
|
|||
|
PWCH nameOnly;
|
|||
|
UNICODE_STRING nameOnlyString;
|
|||
|
UNICODE_STRING shareName;
|
|||
|
|
|||
|
PAGED_CODE( );
|
|||
|
|
|||
|
if( ARGUMENT_PRESENT( ServerName ) ) {
|
|||
|
ServerName->Buffer = NULL;
|
|||
|
ServerName->MaximumLength = ServerName->Length = 0;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the client passed in a malformed type string, then bail out
|
|||
|
//
|
|||
|
if( SrvGetStringLength( ShareTypeString,
|
|||
|
END_OF_REQUEST_SMB( WorkContext ),
|
|||
|
FALSE, TRUE ) == (USHORT)-1 ) {
|
|||
|
|
|||
|
IF_DEBUG(ERRORS) {
|
|||
|
KdPrint(( "SrvVerifyShare: Invalid share type length!\n" ));
|
|||
|
}
|
|||
|
|
|||
|
*Status = STATUS_BAD_DEVICE_TYPE;
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
if( SrvGetStringLength( ShareName,
|
|||
|
END_OF_REQUEST_SMB( WorkContext ),
|
|||
|
ShareNameIsUnicode, TRUE ) == (USHORT)-1 ) {
|
|||
|
|
|||
|
IF_DEBUG(ERRORS) {
|
|||
|
KdPrint(( "SrvVerifyShare: Invalid share name!\n" ));
|
|||
|
}
|
|||
|
|
|||
|
*Status = STATUS_BAD_NETWORK_NAME;
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// First ensure that the share type string is valid.
|
|||
|
//
|
|||
|
|
|||
|
if ( _stricmp( StrShareTypeNames[ShareTypeDisk], ShareTypeString ) == 0 ) {
|
|||
|
shareType = ShareTypeDisk;
|
|||
|
} else if ( _stricmp( StrShareTypeNames[ShareTypePipe], ShareTypeString ) == 0 ) {
|
|||
|
shareType = ShareTypePipe;
|
|||
|
} else if ( _stricmp( StrShareTypeNames[ShareTypePrint], ShareTypeString ) == 0 ) {
|
|||
|
shareType = ShareTypePrint;
|
|||
|
} else if ( _stricmp( StrShareTypeNames[ShareTypeWild], ShareTypeString ) == 0 ) {
|
|||
|
anyShareType = TRUE;
|
|||
|
} else {
|
|||
|
IF_DEBUG(ERRORS) {
|
|||
|
SrvPrint1( "SrvVerifyShare: Invalid share type: %s\n",
|
|||
|
ShareTypeString );
|
|||
|
}
|
|||
|
*Status = STATUS_BAD_DEVICE_TYPE;
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// If the passed-in server\share combination is not Unicode, convert
|
|||
|
// it to Unicode.
|
|||
|
//
|
|||
|
|
|||
|
if ( ShareNameIsUnicode ) {
|
|||
|
ShareName = ALIGN_SMB_WSTR( ShareName );
|
|||
|
}
|
|||
|
|
|||
|
if ( !NT_SUCCESS(SrvMakeUnicodeString(
|
|||
|
ShareNameIsUnicode,
|
|||
|
&shareName,
|
|||
|
ShareName,
|
|||
|
NULL
|
|||
|
)) ) {
|
|||
|
IF_DEBUG(ERRORS) {
|
|||
|
SrvPrint0( "SrvVerifyShare: Unable to allocate heap for Unicode share name string\n" );
|
|||
|
}
|
|||
|
*Status = STATUS_INSUFF_SERVER_RESOURCES;
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Skip past the "\\server\" part of the input string. If there is
|
|||
|
// no leading "\\", assume that the input string contains the share
|
|||
|
// name only. If there is a "\\", but no subsequent "\", assume
|
|||
|
// that the input string contains just a server name, and points to
|
|||
|
// the end of that name, thus fabricating a null share name.
|
|||
|
//
|
|||
|
|
|||
|
nameOnly = shareName.Buffer;
|
|||
|
|
|||
|
|
|||
|
if ( (*nameOnly == DIRECTORY_SEPARATOR_CHAR) &&
|
|||
|
(*(nameOnly+1) == DIRECTORY_SEPARATOR_CHAR) ) {
|
|||
|
|
|||
|
PWSTR nextSlash;
|
|||
|
|
|||
|
|
|||
|
nameOnly += 2;
|
|||
|
nextSlash = wcschr( nameOnly, DIRECTORY_SEPARATOR_CHAR );
|
|||
|
|
|||
|
if( ShareNameIsUnicode && ARGUMENT_PRESENT( ServerName ) ) {
|
|||
|
ServerName->Buffer = nameOnly;
|
|||
|
ServerName->MaximumLength = ServerName->Length = (USHORT)((nextSlash - nameOnly) * sizeof( WCHAR ));
|
|||
|
}
|
|||
|
|
|||
|
if ( nextSlash == NULL ) {
|
|||
|
nameOnly = NULL;
|
|||
|
} else {
|
|||
|
nameOnly = nextSlash + 1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
RtlInitUnicodeString( &nameOnlyString, nameOnly );
|
|||
|
|
|||
|
//
|
|||
|
// Try to match share name against available share names.
|
|||
|
//
|
|||
|
|
|||
|
ACQUIRE_LOCK( &SrvShareLock );
|
|||
|
|
|||
|
share = SrvFindShare( &nameOnlyString );
|
|||
|
|
|||
|
if ( share == NULL ) {
|
|||
|
|
|||
|
RELEASE_LOCK( &SrvShareLock );
|
|||
|
|
|||
|
//
|
|||
|
// Perhaps the client is DFS aware. In this case, see if the DFS
|
|||
|
// driver can help us out.
|
|||
|
//
|
|||
|
|
|||
|
|
|||
|
if( ( shareType == ShareTypeDisk || anyShareType == TRUE ) &&
|
|||
|
SMB_CONTAINS_DFS_NAME( WorkContext )) {
|
|||
|
|
|||
|
*Status = DfsFindShareName( &nameOnlyString );
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
*Status = STATUS_BAD_NETWORK_NAME;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
IF_DEBUG(ERRORS) {
|
|||
|
SrvPrint1( "SrvVerifyShare: Unknown share name: %ws\n",
|
|||
|
nameOnly );
|
|||
|
}
|
|||
|
|
|||
|
if ( !ShareNameIsUnicode ) {
|
|||
|
RtlFreeUnicodeString( &shareName );
|
|||
|
}
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
#if SRVNTVERCHK
|
|||
|
//
|
|||
|
// If we are watching out for old client versions or bad domains, do not allow
|
|||
|
// it to connect to this share if it is a disk share
|
|||
|
//
|
|||
|
if( WorkContext->Connection &&
|
|||
|
(share->ShareType == ShareTypeDisk || SrvMinNT5ClientIPCToo) &&
|
|||
|
(WorkContext->Connection->PagedConnection->ClientTooOld ||
|
|||
|
(WorkContext->Session && WorkContext->Session->ClientBadDomain) )) {
|
|||
|
|
|||
|
//
|
|||
|
// This client may not connect to this share!
|
|||
|
//
|
|||
|
RELEASE_LOCK( &SrvShareLock );
|
|||
|
|
|||
|
if ( !ShareNameIsUnicode ) {
|
|||
|
RtlFreeUnicodeString( &shareName );
|
|||
|
}
|
|||
|
|
|||
|
*Status = WorkContext->Connection->PagedConnection->ClientTooOld ?
|
|||
|
STATUS_REVISION_MISMATCH : STATUS_ACCOUNT_RESTRICTION;
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
//
|
|||
|
// If this is the null session, allow it to connect only to IPC$ or
|
|||
|
// to shares specified in the NullSessionShares list.
|
|||
|
//
|
|||
|
|
|||
|
if ( IsNullSession &&
|
|||
|
SrvRestrictNullSessionAccess &&
|
|||
|
( share->ShareType != ShareTypePipe ) ) {
|
|||
|
|
|||
|
BOOLEAN matchFound = FALSE;
|
|||
|
ULONG i;
|
|||
|
|
|||
|
ACQUIRE_LOCK_SHARED( &SrvConfigurationLock );
|
|||
|
|
|||
|
for ( i = 0; SrvNullSessionShares[i] != NULL ; i++ ) {
|
|||
|
|
|||
|
if ( _wcsicmp(
|
|||
|
SrvNullSessionShares[i],
|
|||
|
nameOnlyString.Buffer
|
|||
|
) == 0 ) {
|
|||
|
|
|||
|
matchFound = TRUE;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
RELEASE_LOCK( &SrvConfigurationLock );
|
|||
|
|
|||
|
if ( !matchFound ) {
|
|||
|
|
|||
|
RELEASE_LOCK( &SrvShareLock );
|
|||
|
|
|||
|
IF_DEBUG(ERRORS) {
|
|||
|
SrvPrint0( "SrvVerifyShare: Illegal null session access.\n");
|
|||
|
}
|
|||
|
|
|||
|
if ( !ShareNameIsUnicode ) {
|
|||
|
RtlFreeUnicodeString( &shareName );
|
|||
|
}
|
|||
|
|
|||
|
*Status = STATUS_ACCESS_DENIED;
|
|||
|
return(NULL);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if ( !ShareNameIsUnicode ) {
|
|||
|
RtlFreeUnicodeString( &shareName );
|
|||
|
}
|
|||
|
|
|||
|
if ( anyShareType || (share->ShareType == shareType) ) {
|
|||
|
|
|||
|
//
|
|||
|
// Put share in work context block and reference it.
|
|||
|
//
|
|||
|
|
|||
|
SrvReferenceShare( share );
|
|||
|
|
|||
|
RELEASE_LOCK( &SrvShareLock );
|
|||
|
|
|||
|
WorkContext->Share = share;
|
|||
|
return share;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
RELEASE_LOCK( &SrvShareLock );
|
|||
|
|
|||
|
IF_DEBUG(ERRORS) {
|
|||
|
SrvPrint1( "SrvVerifyShare: incorrect share type: %s\n",
|
|||
|
ShareTypeString );
|
|||
|
}
|
|||
|
|
|||
|
*Status = STATUS_BAD_DEVICE_TYPE;
|
|||
|
return NULL;
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
} // SrvVerifyShare
|
|||
|
|
|||
|
|
|||
|
PSHARE
|
|||
|
SrvFindShare (
|
|||
|
IN PUNICODE_STRING ShareName
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
Attempts to find a share that matches a given name.
|
|||
|
|
|||
|
*** This routine must be called with the share lock (SrvShareLock)
|
|||
|
held.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
ShareName - name of share to Find.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
A pointer to a share matching the given name, or NULL if none exists.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PSHARE share;
|
|||
|
PLIST_ENTRY listEntryRoot, listEntry;
|
|||
|
ULONG hashValue;
|
|||
|
|
|||
|
PAGED_CODE( );
|
|||
|
|
|||
|
//
|
|||
|
// Try to match share name against available share names.
|
|||
|
//
|
|||
|
|
|||
|
COMPUTE_STRING_HASH( ShareName, &hashValue );
|
|||
|
listEntryRoot = &SrvShareHashTable[ HASH_TO_SHARE_INDEX( hashValue ) ];
|
|||
|
|
|||
|
for( listEntry = listEntryRoot->Flink;
|
|||
|
listEntry != listEntryRoot;
|
|||
|
listEntry = listEntry->Flink ) {
|
|||
|
|
|||
|
share = CONTAINING_RECORD( listEntry, SHARE, GlobalShareList );
|
|||
|
|
|||
|
if( share->ShareNameHashValue == hashValue &&
|
|||
|
RtlCompareUnicodeString(
|
|||
|
&share->ShareName,
|
|||
|
ShareName,
|
|||
|
TRUE
|
|||
|
) == 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// Found a matching share. If it is active return its
|
|||
|
// address.
|
|||
|
//
|
|||
|
|
|||
|
if ( GET_BLOCK_STATE( share ) == BlockStateActive ) {
|
|||
|
return share;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
//
|
|||
|
// Couldn't find a matching share that was active.
|
|||
|
//
|
|||
|
|
|||
|
return NULL;
|
|||
|
|
|||
|
} // SrvFindShare
|
|||
|
|
|||
|
VOID
|
|||
|
SrvRemoveShare(
|
|||
|
PSHARE Share
|
|||
|
)
|
|||
|
{
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
RemoveEntryList( &Share->GlobalShareList );
|
|||
|
}
|
|||
|
|
|||
|
VOID
|
|||
|
SrvAddShare(
|
|||
|
PSHARE Share
|
|||
|
)
|
|||
|
{
|
|||
|
PLIST_ENTRY listEntryRoot, listEntry;
|
|||
|
ULONG hashValue;
|
|||
|
|
|||
|
PAGED_CODE();
|
|||
|
|
|||
|
COMPUTE_STRING_HASH( &Share->ShareName, &hashValue );
|
|||
|
Share->ShareNameHashValue = hashValue;
|
|||
|
listEntryRoot = &SrvShareHashTable[ HASH_TO_SHARE_INDEX( hashValue ) ];
|
|||
|
|
|||
|
InsertTailList( listEntryRoot, &Share->GlobalShareList );
|
|||
|
}
|
|||
|
|
|||
|
NTSTATUS
|
|||
|
SrvShareEnumApiHandler (
|
|||
|
IN PSERVER_REQUEST_PACKET Srp,
|
|||
|
IN PVOID OutputBuffer,
|
|||
|
IN ULONG BufferLength,
|
|||
|
IN PENUM_FILTER_ROUTINE FilterRoutine,
|
|||
|
IN PENUM_SIZE_ROUTINE SizeRoutine,
|
|||
|
IN PENUM_FILL_ROUTINE FillRoutine
|
|||
|
)
|
|||
|
|
|||
|
/*++
|
|||
|
|
|||
|
Routine Description:
|
|||
|
|
|||
|
All share Enum and GetInfo APIs are handled by this routine in the server
|
|||
|
FSD. It takes the ResumeHandle in the SRP to find the first
|
|||
|
appropriate share, then calls the passed-in filter routine to check
|
|||
|
if the share should be filled in. If it should, we call the filter
|
|||
|
routine, then try to get another shar. This continues until the
|
|||
|
entire list has been walked.
|
|||
|
|
|||
|
Arguments:
|
|||
|
|
|||
|
Srp - a pointer to the SRP for the operation.
|
|||
|
|
|||
|
OutputBuffer - the buffer in which to fill output information.
|
|||
|
|
|||
|
BufferLength - the length of the buffer.
|
|||
|
|
|||
|
FilterRoutine - a pointer to a function that will check a share entry
|
|||
|
against information in the SRP to determine whether the
|
|||
|
information in the share should be placed in the output
|
|||
|
buffer.
|
|||
|
|
|||
|
SizeRoutine - a pointer to a function that will find the total size
|
|||
|
a single share will take up in the output buffer. This routine
|
|||
|
is used to check whether we should bother to call the fill
|
|||
|
routine.
|
|||
|
|
|||
|
FillRoutine - a pointer to a function that will fill in the output
|
|||
|
buffer with information from a share.
|
|||
|
|
|||
|
Return Value:
|
|||
|
|
|||
|
NTSTATUS - results of operation.
|
|||
|
|
|||
|
--*/
|
|||
|
|
|||
|
{
|
|||
|
PSHARE share;
|
|||
|
ULONG totalEntries;
|
|||
|
ULONG entriesRead;
|
|||
|
ULONG bytesRequired;
|
|||
|
|
|||
|
PCHAR fixedStructurePointer;
|
|||
|
PCHAR variableData;
|
|||
|
ULONG blockSize;
|
|||
|
|
|||
|
BOOLEAN bufferOverflow = FALSE;
|
|||
|
BOOLEAN entryReturned = FALSE;
|
|||
|
|
|||
|
PLIST_ENTRY listEntryRoot, listEntry;
|
|||
|
ULONG oldSkipCount;
|
|||
|
ULONG newResumeKey;
|
|||
|
|
|||
|
PAGED_CODE( );
|
|||
|
|
|||
|
//
|
|||
|
// Set up local variables.
|
|||
|
//
|
|||
|
|
|||
|
fixedStructurePointer = OutputBuffer;
|
|||
|
variableData = fixedStructurePointer + BufferLength;
|
|||
|
variableData = (PCHAR)((ULONG_PTR)variableData & ~1);
|
|||
|
|
|||
|
entriesRead = 0;
|
|||
|
totalEntries = 0;
|
|||
|
bytesRequired = 0;
|
|||
|
|
|||
|
listEntryRoot = &SrvShareHashTable[ HASH_TO_SHARE_INDEX( Srp->Parameters.Get.ResumeHandle >> 16 ) ];
|
|||
|
oldSkipCount = Srp->Parameters.Get.ResumeHandle & 0xff;
|
|||
|
|
|||
|
ACQUIRE_LOCK_SHARED( &SrvShareLock );
|
|||
|
|
|||
|
for( ;
|
|||
|
listEntryRoot < &SrvShareHashTable[ NSHARE_HASH_TABLE ];
|
|||
|
listEntryRoot++, newResumeKey = 0 ) {
|
|||
|
|
|||
|
newResumeKey = (ULONG)((listEntryRoot - SrvShareHashTable) << 16);
|
|||
|
|
|||
|
for( listEntry = listEntryRoot->Flink;
|
|||
|
listEntry != listEntryRoot;
|
|||
|
listEntry = listEntry->Flink, newResumeKey++ ) {
|
|||
|
|
|||
|
if( oldSkipCount ) {
|
|||
|
--oldSkipCount;
|
|||
|
++newResumeKey;
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
share = CONTAINING_RECORD( listEntry, SHARE, GlobalShareList );
|
|||
|
|
|||
|
//
|
|||
|
// Call the filter routine to determine whether we should
|
|||
|
// return this share.
|
|||
|
//
|
|||
|
|
|||
|
if ( FilterRoutine( Srp, share ) ) {
|
|||
|
|
|||
|
blockSize = SizeRoutine( Srp, share );
|
|||
|
|
|||
|
totalEntries++;
|
|||
|
bytesRequired += blockSize;
|
|||
|
|
|||
|
//
|
|||
|
// If all the information in the share will fit in the
|
|||
|
// output buffer, write it. Otherwise, indicate that there
|
|||
|
// was an overflow. As soon as an entry doesn't fit, stop
|
|||
|
// putting them in the buffer. This ensures that the resume
|
|||
|
// mechanism will work--retuning partial entries would make
|
|||
|
// it nearly impossible to use the resumability of the APIs,
|
|||
|
// since the caller would have to resume from an imcomplete
|
|||
|
// entry.
|
|||
|
//
|
|||
|
|
|||
|
if ( (ULONG_PTR)fixedStructurePointer + blockSize <=
|
|||
|
(ULONG_PTR)variableData && !bufferOverflow ) {
|
|||
|
|
|||
|
FillRoutine(
|
|||
|
Srp,
|
|||
|
share,
|
|||
|
(PVOID *)&fixedStructurePointer,
|
|||
|
(LPWSTR *)&variableData
|
|||
|
);
|
|||
|
|
|||
|
entriesRead++;
|
|||
|
newResumeKey++;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
bufferOverflow = TRUE;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
RELEASE_LOCK( &SrvShareLock );
|
|||
|
|
|||
|
//
|
|||
|
// Set the information to pass back to the server service.
|
|||
|
//
|
|||
|
|
|||
|
Srp->Parameters.Get.EntriesRead = entriesRead;
|
|||
|
Srp->Parameters.Get.TotalEntries = totalEntries;
|
|||
|
Srp->Parameters.Get.TotalBytesNeeded = bytesRequired;
|
|||
|
|
|||
|
//
|
|||
|
// Return appropriate status.
|
|||
|
//
|
|||
|
|
|||
|
if ( entriesRead == 0 && totalEntries > 0 ) {
|
|||
|
|
|||
|
//
|
|||
|
// Not even a single entry fit.
|
|||
|
//
|
|||
|
|
|||
|
Srp->ErrorCode = NERR_BufTooSmall;
|
|||
|
return STATUS_SUCCESS;
|
|||
|
|
|||
|
} else if ( bufferOverflow ) {
|
|||
|
|
|||
|
//
|
|||
|
// At least one entry fit, but not all of them.
|
|||
|
//
|
|||
|
|
|||
|
Srp->ErrorCode = ERROR_MORE_DATA;
|
|||
|
Srp->Parameters.Get.ResumeHandle = newResumeKey;
|
|||
|
return STATUS_SUCCESS;
|
|||
|
|
|||
|
} else {
|
|||
|
|
|||
|
//
|
|||
|
// All entries fit.
|
|||
|
//
|
|||
|
|
|||
|
Srp->ErrorCode = NO_ERROR;
|
|||
|
Srp->Parameters.Get.ResumeHandle = 0;
|
|||
|
return STATUS_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
} // SrvEnumApiHandler
|