7147 lines
185 KiB
C
7147 lines
185 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
smbsupp.c
|
||
|
||
Abstract:
|
||
|
||
This module contains various support routines for processing SMBs.
|
||
|
||
Author:
|
||
|
||
Chuck Lenzmeier (chuckl) 9-Nov-1989
|
||
David Treadwell (davidtr)
|
||
|
||
Revision History:
|
||
|
||
--*/
|
||
|
||
#include "precomp.h"
|
||
#include "smbsupp.tmh"
|
||
#pragma hdrstop
|
||
|
||
#define BugCheckFileId SRV_FILE_SMBSUPP
|
||
|
||
#define CHAR_SP ' '
|
||
|
||
//
|
||
// Mapping is defined in inc\srvfsctl.h
|
||
//
|
||
|
||
STATIC GENERIC_MAPPING SrvFileAccessMapping = GENERIC_SHARE_FILE_ACCESS_MAPPING;
|
||
|
||
//
|
||
// Forward references
|
||
//
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text( PAGE, Srv8dot3ToUnicodeString )
|
||
#pragma alloc_text( PAGE, SrvAllocateAndBuildPathName )
|
||
#pragma alloc_text( PAGE, SrvCanonicalizePathName )
|
||
#pragma alloc_text( PAGE, SrvCanonicalizePathNameWithReparse )
|
||
#pragma alloc_text( PAGE, SrvCheckSearchAttributesForHandle )
|
||
#pragma alloc_text( PAGE, SrvCheckSearchAttributes )
|
||
#pragma alloc_text( PAGE, SrvGetAlertServiceName )
|
||
#pragma alloc_text( PAGE, SrvGetBaseFileName )
|
||
#pragma alloc_text( PAGE, SrvGetMultiSZList )
|
||
#pragma alloc_text( PAGE, SrvGetOsVersionString )
|
||
#pragma alloc_text( PAGE, SrvGetString )
|
||
#pragma alloc_text( PAGE, SrvGetStringLength )
|
||
#pragma alloc_text( PAGE, SrvGetSubdirectoryLength )
|
||
#pragma alloc_text( PAGE, SrvIsLegalFatName )
|
||
#pragma alloc_text( PAGE, SrvMakeUnicodeString )
|
||
//#pragma alloc_text( PAGE, SrvReleaseContext )
|
||
#pragma alloc_text( PAGE, SrvSetFileWritethroughMode )
|
||
#pragma alloc_text( PAGE, SrvOemStringTo8dot3 )
|
||
#pragma alloc_text( PAGE, SrvUnicodeStringTo8dot3 )
|
||
#pragma alloc_text( PAGE, SrvVerifySid )
|
||
#pragma alloc_text( PAGE, SrvVerifyTid )
|
||
#pragma alloc_text( PAGE, SrvVerifyUid )
|
||
#pragma alloc_text( PAGE, SrvVerifyUidAndTid )
|
||
#pragma alloc_text( PAGE, SrvIoCreateFile )
|
||
#pragma alloc_text( PAGE, SrvNtClose )
|
||
#pragma alloc_text( PAGE, SrvVerifyDeviceStackSize )
|
||
#pragma alloc_text( PAGE, SrvImpersonate )
|
||
#pragma alloc_text( PAGE, SrvRevert )
|
||
#pragma alloc_text( PAGE, SrvSetLastWriteTime )
|
||
#pragma alloc_text( PAGE, SrvCheckShareFileAccess )
|
||
#pragma alloc_text( PAGE, SrvReleaseShareRootHandle )
|
||
#pragma alloc_text( PAGE, SrvUpdateVcQualityOfService )
|
||
#pragma alloc_text( PAGE, SrvIsAllowedOnAdminShare )
|
||
#pragma alloc_text( PAGE, SrvRetrieveMaximalAccessRightsForUser )
|
||
#pragma alloc_text( PAGE, SrvRetrieveMaximalAccessRights )
|
||
#pragma alloc_text( PAGE, SrvRetrieveMaximalShareAccessRights )
|
||
#pragma alloc_text( PAGE, SrvUpdateMaximalAccessRightsInResponse )
|
||
#pragma alloc_text( PAGE, SrvUpdateMaximalShareAccessRightsInResponse )
|
||
//#pragma alloc_text( PAGE, SrvValidateSmb )
|
||
#pragma alloc_text( PAGE, SrvWildcardRename )
|
||
#pragma alloc_text( PAGE8FIL, SrvCheckForSavedError )
|
||
#pragma alloc_text( PAGE, SrvIsDottedQuadAddress )
|
||
#endif
|
||
#if 0
|
||
NOT PAGEABLE -- SrvUpdateStatistics2
|
||
NOT PAGEABLE -- SrvVerifyFid2
|
||
NOT PAGEABLE -- SrvVerifyFidForRawWrite
|
||
NOT PAGEABLE -- SrvReceiveBufferShortage
|
||
#endif
|
||
|
||
|
||
VOID
|
||
Srv8dot3ToUnicodeString (
|
||
IN PSZ Input8dot3,
|
||
OUT PUNICODE_STRING OutputString
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Convert FAT 8.3 format into a string.
|
||
|
||
Arguments:
|
||
|
||
Input8dot3 - Supplies the input 8.3 name to convert
|
||
|
||
OutputString - Receives the converted name. The memory must be
|
||
supplied by the caller.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
LONG i;
|
||
CLONG lastOutputChar;
|
||
UCHAR tempBuffer[8+1+3];
|
||
OEM_STRING tempString;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// If we get "." or "..", just return them. They do not follow
|
||
// the usual rules for FAT names.
|
||
//
|
||
|
||
lastOutputChar = 0;
|
||
|
||
if ( Input8dot3[0] == '.' && Input8dot3[1] == '\0' ) {
|
||
|
||
tempBuffer[0] = '.';
|
||
lastOutputChar = 0;
|
||
|
||
} else if ( Input8dot3[0] == '.' && Input8dot3[1] == '.' &&
|
||
Input8dot3[2] == '\0' ) {
|
||
|
||
tempBuffer[0] = '.';
|
||
tempBuffer[1] = '.';
|
||
lastOutputChar = 1;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Copy over the 8 part of the 8.3 name into the output buffer,
|
||
// then back up the index to the first non-space character,
|
||
// searching backwards.
|
||
//
|
||
|
||
RtlCopyMemory( tempBuffer, Input8dot3, 8 );
|
||
|
||
for ( i = 7;
|
||
(i >= 0) && (tempBuffer[i] == CHAR_SP);
|
||
i -- ) {
|
||
;
|
||
}
|
||
|
||
//
|
||
// Add a dot.
|
||
//
|
||
|
||
i++;
|
||
tempBuffer[i] = '.';
|
||
|
||
//
|
||
// Copy over the 3 part of the 8.3 name into the output buffer,
|
||
// then back up the index to the first non-space character,
|
||
// searching backwards.
|
||
//
|
||
|
||
lastOutputChar = i;
|
||
|
||
for ( i = 8; i < 11; i++ ) {
|
||
|
||
//
|
||
// Copy the byte.
|
||
//
|
||
// *** This code used to mask off the top bit. This was a
|
||
// legacy of very ancient times when the bit may have
|
||
// been used as a resume key sequence bit.
|
||
//
|
||
|
||
tempBuffer[++lastOutputChar] = (UCHAR)Input8dot3[i];
|
||
|
||
}
|
||
|
||
while ( tempBuffer[lastOutputChar] == CHAR_SP ) {
|
||
lastOutputChar--;
|
||
}
|
||
|
||
//
|
||
// If the last character is a '.', then we don't have an
|
||
// extension, so back up before the dot.
|
||
//
|
||
|
||
if ( tempBuffer[lastOutputChar] == '.') {
|
||
lastOutputChar--;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Convert to Unicode.
|
||
//
|
||
|
||
tempString.Length = (SHORT)(lastOutputChar + 1);
|
||
tempString.Buffer = tempBuffer;
|
||
|
||
OutputString->MaximumLength =
|
||
(SHORT)((lastOutputChar + 2) * sizeof(WCHAR));
|
||
|
||
RtlOemStringToUnicodeString( OutputString, &tempString, FALSE );
|
||
|
||
return;
|
||
|
||
} // Srv8dot3ToUnicodeString
|
||
|
||
|
||
VOID
|
||
SrvAllocateAndBuildPathName(
|
||
IN PUNICODE_STRING Path1,
|
||
IN PUNICODE_STRING Path2 OPTIONAL,
|
||
IN PUNICODE_STRING Path3 OPTIONAL,
|
||
OUT PUNICODE_STRING BuiltPath
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Allocates space and concatenates the paths in the parameter strings.
|
||
ALLOCATE_HEAP is used to allocate the memory for the full
|
||
pathname. Directory separator characters ('\') are added as
|
||
necessary so that a legitimate path is built. If the third
|
||
parameter is NULL, then only the first two strings are concatenated,
|
||
and if both the second and third parameters are NULL then the first
|
||
string is simply copied over to a new location.
|
||
|
||
Arguments:
|
||
|
||
Input8dot3 - Supplies the input 8.3 name to convert
|
||
|
||
OutputString - Receives the converted name, the memory must be supplied
|
||
by the caller.
|
||
|
||
Return Value:
|
||
|
||
None
|
||
|
||
--*/
|
||
|
||
{
|
||
UNICODE_STRING path2;
|
||
UNICODE_STRING path3;
|
||
PWCH nextLocation;
|
||
PWSTR pathBuffer;
|
||
ULONG allocationLength;
|
||
WCHAR nullString = 0;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// Set up the strings for optional parameters Path2 and Path3. Doing
|
||
// this allows later code to be ignorant of whether the strings were
|
||
// actually passed.
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT(Path2) ) {
|
||
|
||
path2.Buffer = Path2->Buffer;
|
||
path2.Length = Path2->Length;
|
||
|
||
} else {
|
||
|
||
path2.Buffer = &nullString;
|
||
path2.Length = 0;
|
||
}
|
||
|
||
if ( ARGUMENT_PRESENT(Path3) ) {
|
||
|
||
path3.Buffer = Path3->Buffer;
|
||
path3.Length = Path3->Length;
|
||
|
||
} else {
|
||
|
||
path3.Buffer = &nullString;
|
||
path3.Length = 0;
|
||
}
|
||
|
||
//
|
||
// Allocate space in which to put the path name we are building.
|
||
// The +3 if to account for as many as two directory separator
|
||
// characters being added and the zero terminator at the end. This
|
||
// has a small cost in terms of memory usage, but it simplifies this
|
||
// code.
|
||
//
|
||
// The calling routine must be careful to deallocate this space
|
||
// when it is done with the path name.
|
||
//
|
||
|
||
allocationLength = Path1->Length + path2.Length + path3.Length +
|
||
3 * sizeof(WCHAR);
|
||
|
||
pathBuffer = ALLOCATE_HEAP_COLD(
|
||
allocationLength,
|
||
BlockTypeDataBuffer
|
||
);
|
||
if ( pathBuffer == NULL ) {
|
||
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_EXPECTED,
|
||
"SrvAllocateAndBuildPathName: Unable to allocate %d bytes "
|
||
"from heap.",
|
||
allocationLength,
|
||
NULL
|
||
);
|
||
|
||
BuiltPath->Buffer = NULL;
|
||
return;
|
||
}
|
||
|
||
BuiltPath->Buffer = pathBuffer;
|
||
BuiltPath->MaximumLength = (USHORT)allocationLength;
|
||
|
||
ASSERT ( ( allocationLength & 0xffff0000 ) == 0 );
|
||
|
||
RtlZeroMemory( pathBuffer, allocationLength );
|
||
|
||
//
|
||
// Copy the first path name to the space we have allocated.
|
||
//
|
||
|
||
RtlCopyMemory( pathBuffer, Path1->Buffer, Path1->Length );
|
||
nextLocation = (PWCH)((PCHAR)pathBuffer + Path1->Length);
|
||
|
||
//
|
||
// If there was no separator character at the end of the first path
|
||
// or at the beginning of the next path, put one in. We don't
|
||
// want to put in leading slashes, however, so don't put one in
|
||
// if it would be the first character. Also, we don't want to insert
|
||
// a slash if a relative stream is being opened (i.e. name begins with ':')
|
||
//
|
||
|
||
if ( nextLocation > pathBuffer &&
|
||
*(nextLocation - 1) != DIRECTORY_SEPARATOR_CHAR &&
|
||
*path2.Buffer != DIRECTORY_SEPARATOR_CHAR &&
|
||
*path2.Buffer != RELATIVE_STREAM_INITIAL_CHAR ) {
|
||
|
||
*nextLocation++ = DIRECTORY_SEPARATOR_CHAR;
|
||
}
|
||
|
||
//
|
||
// Concatenate the second path name with the first.
|
||
//
|
||
|
||
RtlCopyMemory( nextLocation, path2.Buffer, path2.Length );
|
||
nextLocation = (PWCH)((PCHAR)nextLocation + path2.Length);
|
||
|
||
//
|
||
// If there was no separator character at the end of the first path
|
||
// or at the beginning of the next path, put one in. Again, don't
|
||
// put in leading slashes, and watch out for relative stream opens.
|
||
//
|
||
|
||
if ( nextLocation > pathBuffer &&
|
||
*(nextLocation - 1) != DIRECTORY_SEPARATOR_CHAR &&
|
||
*path3.Buffer != DIRECTORY_SEPARATOR_CHAR &&
|
||
*path3.Buffer != RELATIVE_STREAM_INITIAL_CHAR ) {
|
||
|
||
*nextLocation++ = DIRECTORY_SEPARATOR_CHAR;
|
||
}
|
||
|
||
//
|
||
// Concatenate the third path name.
|
||
//
|
||
|
||
RtlCopyMemory( nextLocation, path3.Buffer, path3.Length );
|
||
nextLocation = (PWCH)((PCHAR)nextLocation + path3.Length);
|
||
|
||
//
|
||
// The path cannot end in a '\', so if there was one at the end get
|
||
// rid of it.
|
||
//
|
||
|
||
if ( nextLocation > pathBuffer &&
|
||
*(nextLocation - 1) == DIRECTORY_SEPARATOR_CHAR ) {
|
||
*(--nextLocation) = '\0';
|
||
}
|
||
|
||
//
|
||
// Find the length of the path we built.
|
||
//
|
||
|
||
BuiltPath->Length = (SHORT)((PCHAR)nextLocation - (PCHAR)pathBuffer);
|
||
|
||
return;
|
||
|
||
} // SrvAllocateAndBuildPathName
|
||
|
||
NTSTATUS
|
||
SrvCanonicalizePathName(
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN PSHARE Share OPTIONAL,
|
||
IN PUNICODE_STRING RelatedPath OPTIONAL,
|
||
IN OUT PVOID Name,
|
||
IN PCHAR LastValidLocation,
|
||
IN BOOLEAN RemoveTrailingDots,
|
||
IN BOOLEAN SourceIsUnicode,
|
||
OUT PUNICODE_STRING String
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine canonicalizes a filename. All ".\" are removed, and
|
||
"..\" are evaluated to go up a directory level. A check is also
|
||
made to ensure that the pathname does not go to a directory above
|
||
the share root directory (i.e., no leading "..\"). Trailing blanks
|
||
are always removed, as are trailing dots if RemoveTrailingDots is
|
||
TRUE.
|
||
|
||
If the input string is not Unicode, a Unicode representation of the
|
||
input is obtained. This requires that additional space be
|
||
allocated, and it is the caller's responsibility to free this space.
|
||
|
||
If the input string IS Unicode, this routine will align the input
|
||
pointer (Name) to the next two-byte boundary before performing the
|
||
canonicalization. All Unicode strings in SMBs must be aligned
|
||
properly.
|
||
|
||
This routine operates "in place," meaning that it puts the
|
||
canonicalized pathname in the same storage as the uncanonicalized
|
||
pathname. This is useful for operating on the Buffer fields of the
|
||
request SMBs--simply call this routine and it will fix the pathname.
|
||
However, the calling routine must be careful if there are two
|
||
pathnames stored in the buffer field--the second won't necessarily
|
||
start in the space just after the first '\0'.
|
||
|
||
The LastValidLocation parameter is used to determine the maximum
|
||
possible length of the name. This prevents an access violation if
|
||
the client fails to include a zero terminator, or for strings (such
|
||
as the file name in NT Create And X) that are not required to be
|
||
zero terminated.
|
||
|
||
If the SMB described by WorkContext is marked as containing Dfs names,
|
||
this routine will additionally call the Dfs driver to translate the
|
||
Dfs name to a path relative to the Share. Since this call to the Dfs
|
||
driver is NOT idempotent, the SMB flag indicating that it contains a
|
||
Dfs name is CLEARED after a call to this routine. This posses a problem
|
||
for the few SMBs that contain multiple names. The handlers for those
|
||
SMBs must make sure that they conditionally call the SMB_MARK_AS_DFS_NAME
|
||
macro before calling this routine.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - contains information about the negotiated dialect. This
|
||
is used for deciding whether to strip trailing spaces and dots.
|
||
|
||
Share - a pointer to the share entry
|
||
|
||
Name - a pointer to the filename to canonicalize.
|
||
|
||
LastValidLocation - a pointer to the last valid location in the
|
||
buffer pointed to by Name.
|
||
|
||
RemoveTrailingDots - if TRUE, trailing dots are removed. Otherwise,
|
||
they are left in (this supports special behavior needed by
|
||
directory search logic).
|
||
|
||
SourceIsUnicode - if TRUE, the input is canonicalized in place.
|
||
If FALSE, the input is first converted to Unicode, then
|
||
canonicalized.
|
||
|
||
String - a pointer to string descriptor.
|
||
|
||
Return Value:
|
||
|
||
BOOLEAN - FALSE if the name was invalid or if storage for the
|
||
Unicode string could not be obtained.
|
||
|
||
--*/
|
||
|
||
{
|
||
PWCH source, destination, lastComponent, name;
|
||
BOOLEAN notNtClient;
|
||
NTSTATUS status = STATUS_SUCCESS;
|
||
DWORD numberOfPathElements = 0;
|
||
|
||
PAGED_CODE( );
|
||
|
||
#if DBG
|
||
return SrvCanonicalizePathNameWithReparse( WorkContext, Share, RelatedPath, Name, LastValidLocation, RemoveTrailingDots, SourceIsUnicode, String );
|
||
#else
|
||
if( SMB_IS_UNICODE( WorkContext ) &&
|
||
FlagOn( WorkContext->RequestHeader->Flags2, SMB_FLAGS2_REPARSE_PATH ) )
|
||
{
|
||
return SrvCanonicalizePathNameWithReparse( WorkContext, Share, RelatedPath, Name, LastValidLocation, RemoveTrailingDots, SourceIsUnicode, String );
|
||
}
|
||
#endif
|
||
|
||
|
||
notNtClient = !IS_NT_DIALECT( WorkContext->Connection->SmbDialect );
|
||
|
||
if ( SourceIsUnicode ) {
|
||
|
||
//
|
||
// The source string is already Unicode. Align the pointer.
|
||
// Save the character at the last location in the buffer, then
|
||
// set that location to zero. This prevents any loops from
|
||
// going past the end of the buffer.
|
||
//
|
||
|
||
name = ALIGN_SMB_WSTR(Name);
|
||
String->Buffer = name;
|
||
|
||
} else {
|
||
|
||
OEM_STRING oemString;
|
||
PCHAR p;
|
||
ULONG length;
|
||
|
||
//
|
||
// The source string is not Unicode. Determine the length of
|
||
// the string by finding the zero terminator or the end of the
|
||
// input buffer. We need the length in order to convert the
|
||
// string to Unicode, and we can't just call RtlInitString, in
|
||
// case the string isn't terminated.
|
||
//
|
||
|
||
for ( p = Name, length = 0;
|
||
p <= LastValidLocation && *p != 0;
|
||
p++, length++ ) {
|
||
;
|
||
}
|
||
|
||
//
|
||
// Convert the source string to Unicode.
|
||
//
|
||
|
||
oemString.Buffer = Name;
|
||
oemString.Length = (USHORT)length;
|
||
oemString.MaximumLength = (USHORT)length;
|
||
|
||
status = RtlOemStringToUnicodeString(
|
||
String,
|
||
&oemString,
|
||
TRUE
|
||
);
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
return status;
|
||
}
|
||
|
||
name = (PWCH)String->Buffer;
|
||
LastValidLocation = (PCHAR)String->Buffer + String->Length;
|
||
|
||
}
|
||
|
||
//
|
||
// Though everything is done in place, separate source and
|
||
// destination pointers are maintained. It is necessary that source
|
||
// >= destination at all times to avoid writing into space we
|
||
// haven't looked at yet. The three main operations performed by
|
||
// this routine ( ".\", "..\", and getting rid of trailing "." and "
|
||
// ") do not interfere with this goal.
|
||
//
|
||
|
||
destination = name;
|
||
source = name;
|
||
|
||
//
|
||
// The lastComponent variable is used as a placeholder when
|
||
// backtracking over trailing blanks and dots. It points to the
|
||
// first character after the last directory separator or the
|
||
// beginning of the pathname.
|
||
//
|
||
|
||
lastComponent = destination;
|
||
|
||
//
|
||
// Get rid of leading directory separators.
|
||
//
|
||
|
||
while ( source <= (PWCH)LastValidLocation &&
|
||
(*source == UNICODE_DIR_SEPARATOR_CHAR) && (*source != L'\0') ) {
|
||
source++;
|
||
}
|
||
|
||
//
|
||
// Walk through the pathname until we reach the zero terminator. At
|
||
// the start of this loop, source points to the first charaecter
|
||
// after a directory separator or the first character of the
|
||
// pathname.
|
||
//
|
||
|
||
while ( (source <= (PWCH)LastValidLocation) && (*source != L'\0') ) {
|
||
|
||
if ( *source == L'.' ) {
|
||
|
||
//
|
||
// If we see a dot, look at the next character.
|
||
//
|
||
|
||
if ( notNtClient &&
|
||
((source+1) <= (PWCH)LastValidLocation) &&
|
||
(*(source+1) == UNICODE_DIR_SEPARATOR_CHAR) ) {
|
||
|
||
//
|
||
// If the next character is a directory separator,
|
||
// advance the source pointer to the directory
|
||
// separator.
|
||
//
|
||
|
||
source += 1;
|
||
|
||
} else if ( ((source+1) <= (PWCH)LastValidLocation) &&
|
||
(*(source+1) == L'.') &&
|
||
((source+1) == (PWCH)LastValidLocation ||
|
||
IS_UNICODE_PATH_SEPARATOR( *(source+2) ))) {
|
||
|
||
//
|
||
// If the following characters are ".\", we have a "..\".
|
||
// Advance the source pointer to the "\".
|
||
//
|
||
|
||
source += 2;
|
||
|
||
//
|
||
// Move the destination pointer to the charecter before the
|
||
// last directory separator in order to prepare for backing
|
||
// up. This may move the pointer before the beginning of
|
||
// the name pointer.
|
||
//
|
||
|
||
destination -= 2;
|
||
|
||
//
|
||
// If destination points before the beginning of the name
|
||
// pointer, fail because the user is attempting to go
|
||
// to a higher directory than the share root. This is
|
||
// the equivalent of a leading "..\", but may result from
|
||
// a case like "dir\..\..\file".
|
||
//
|
||
|
||
if ( destination <= name ) {
|
||
if ( !SourceIsUnicode ) {
|
||
RtlFreeUnicodeString( String );
|
||
String->Buffer = NULL;
|
||
}
|
||
return STATUS_OBJECT_PATH_SYNTAX_BAD;
|
||
}
|
||
|
||
//
|
||
// Back up the destination pointer to after the last
|
||
// directory separator or to the beginning of the pathname.
|
||
// Backup to the beginning of the pathname will occur
|
||
// in a case like "dir\..\file".
|
||
//
|
||
|
||
while ( destination >= name &&
|
||
*destination != UNICODE_DIR_SEPARATOR_CHAR ) {
|
||
destination--;
|
||
}
|
||
|
||
//
|
||
// destination points to \ or character before name; we
|
||
// want it to point to character after last \.
|
||
//
|
||
|
||
destination++;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The characters after the dot are not "\" or ".\", so
|
||
// so just copy source to destination until we reach a
|
||
// directory separator character. This will occur in
|
||
// a case like ".file" (filename starts with a dot).
|
||
//
|
||
|
||
do {
|
||
*destination++ = *source++;
|
||
} while ( (source <= (PWCH)LastValidLocation) &&
|
||
!IS_UNICODE_PATH_SEPARATOR( *source ) );
|
||
|
||
numberOfPathElements++;
|
||
|
||
}
|
||
|
||
} else { // if ( *source == L'.' )
|
||
|
||
//
|
||
// source does not point to a dot, so copy source to
|
||
// destination until we get to a directory separator.
|
||
//
|
||
|
||
while ( (source <= (PWCH)LastValidLocation) &&
|
||
!IS_UNICODE_PATH_SEPARATOR( *source ) ) {
|
||
*destination++ = *source++;
|
||
}
|
||
|
||
numberOfPathElements++;
|
||
|
||
}
|
||
|
||
//
|
||
// Truncate trailing dots and blanks. destination should point
|
||
// to the last character before the directory separator, so back
|
||
// up over blanks and dots.
|
||
//
|
||
|
||
if ( notNtClient ) {
|
||
|
||
while ( ( destination > lastComponent ) &&
|
||
( (RemoveTrailingDots && *(destination-1) == '.')
|
||
|| *(destination-1) == ' ' ) ) {
|
||
destination--;
|
||
}
|
||
}
|
||
|
||
//
|
||
// At this point, source points to a directory separator or to
|
||
// a zero terminator. If it is a directory separator, put one
|
||
// in the destination.
|
||
//
|
||
|
||
if ( (source <= (PWCH)LastValidLocation) &&
|
||
(*source == UNICODE_DIR_SEPARATOR_CHAR) ) {
|
||
|
||
//
|
||
// If we haven't put the directory separator in the path name,
|
||
// put it in.
|
||
//
|
||
|
||
if ( destination != name &&
|
||
*(destination-1) != UNICODE_DIR_SEPARATOR_CHAR ) {
|
||
|
||
*destination++ = UNICODE_DIR_SEPARATOR_CHAR;
|
||
|
||
}
|
||
|
||
//
|
||
// It is legal to have multiple directory separators, so get
|
||
// rid of them here. Example: "dir\\\\\\\\file".
|
||
//
|
||
|
||
do {
|
||
source++;
|
||
} while ( (source <= (PWCH)LastValidLocation) &&
|
||
(*source == UNICODE_DIR_SEPARATOR_CHAR) );
|
||
|
||
//
|
||
// Make lastComponent point to the character after the directory
|
||
// separator.
|
||
//
|
||
|
||
lastComponent = destination;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// We're just about done. If there was a trailing .. (example:
|
||
// "file\.."), trailing . ("file\."), or multiple trailing
|
||
// separators ("file\\\\"), then back up one since separators are
|
||
// illegal at the end of a pathname.
|
||
//
|
||
|
||
if ( destination > name &&
|
||
*(destination-1) == UNICODE_DIR_SEPARATOR_CHAR ) {
|
||
|
||
destination--;
|
||
}
|
||
|
||
*destination = L'\0';
|
||
|
||
//
|
||
// The length of the destination string is the difference between the
|
||
// destination pointer (points to zero terminator at this point)
|
||
// and the name pointer (points to the beginning of the destination
|
||
// string).
|
||
//
|
||
|
||
String->Length = (SHORT)((PCHAR)destination - (PCHAR)name);
|
||
String->MaximumLength = String->Length;
|
||
|
||
//
|
||
// One final thing: Is this SMB referring to a DFS name? If so, ask
|
||
// the DFS driver to turn it into a local name.
|
||
//
|
||
if( ARGUMENT_PRESENT( Share ) &&
|
||
Share->IsDfs &&
|
||
SMB_CONTAINS_DFS_NAME( WorkContext )) {
|
||
|
||
BOOLEAN stripLastComponent = FALSE;
|
||
|
||
//
|
||
// We have to special case some SMBs (like TRANS2_FIND_FIRST2)
|
||
// because they contain path Dfs path names that could refer to a
|
||
// junction point. The SMB handlers for these SMBs are not interested
|
||
// in a STATUS_PATH_NOT_COVERED error; instead they want the name
|
||
// to be resolved to the the junction point.
|
||
//
|
||
|
||
if (WorkContext->NextCommand == SMB_COM_TRANSACTION2 ) {
|
||
|
||
PTRANSACTION transaction;
|
||
USHORT command;
|
||
|
||
transaction = WorkContext->Parameters.Transaction;
|
||
command = SmbGetUshort( &transaction->InSetup[0] );
|
||
|
||
if (command == TRANS2_FIND_FIRST2 && numberOfPathElements > 2 )
|
||
stripLastComponent = TRUE;
|
||
|
||
}
|
||
|
||
status =
|
||
DfsNormalizeName(Share, RelatedPath, stripLastComponent, String);
|
||
|
||
SMB_MARK_AS_DFS_TRANSLATED( WorkContext );
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
if ( !SourceIsUnicode ) {
|
||
RtlFreeUnicodeString( String );
|
||
String->Buffer = NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
return status;
|
||
|
||
} // SrvCanonicalizePathName
|
||
|
||
NTSTATUS
|
||
SrvCanonicalizePathNameWithReparse(
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN PSHARE Share OPTIONAL,
|
||
IN PUNICODE_STRING RelatedPath OPTIONAL,
|
||
IN OUT PVOID Name,
|
||
IN PCHAR LastValidLocation,
|
||
IN BOOLEAN RemoveTrailingDots,
|
||
IN BOOLEAN SourceIsUnicode,
|
||
OUT PUNICODE_STRING String
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine is identical to the one above with the exception that it
|
||
checks the path for reparse-able names (such as snapshot references) and
|
||
handles them accordingly. This allows us to present new features within
|
||
the Win32 namespace so old applications can use them.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - contains information about the negotiated dialect. This
|
||
is used for deciding whether to strip trailing spaces and dots.
|
||
|
||
Share - a pointer to the share entry
|
||
|
||
Name - a pointer to the filename to canonicalize.
|
||
|
||
LastValidLocation - a pointer to the last valid location in the
|
||
buffer pointed to by Name.
|
||
|
||
RemoveTrailingDots - if TRUE, trailing dots are removed. Otherwise,
|
||
they are left in (this supports special behavior needed by
|
||
directory search logic).
|
||
|
||
SourceIsUnicode - if TRUE, the input is canonicalized in place.
|
||
If FALSE, the input is first converted to Unicode, then
|
||
canonicalized.
|
||
|
||
String - a pointer to string descriptor.
|
||
|
||
Return Value:
|
||
|
||
BOOLEAN - FALSE if the name was invalid or if storage for the
|
||
Unicode string could not be obtained.
|
||
|
||
--*/
|
||
|
||
{
|
||
PWCH source, destination, lastComponent, name;
|
||
BOOLEAN notNtClient;
|
||
NTSTATUS status = STATUS_SUCCESS;
|
||
DWORD numberOfPathElements = 0;
|
||
|
||
PAGED_CODE( );
|
||
|
||
notNtClient = !IS_NT_DIALECT( WorkContext->Connection->SmbDialect );
|
||
|
||
if ( SourceIsUnicode ) {
|
||
|
||
//
|
||
// The source string is already Unicode. Align the pointer.
|
||
// Save the character at the last location in the buffer, then
|
||
// set that location to zero. This prevents any loops from
|
||
// going past the end of the buffer.
|
||
//
|
||
|
||
name = ALIGN_SMB_WSTR(Name);
|
||
String->Buffer = name;
|
||
|
||
} else {
|
||
|
||
OEM_STRING oemString;
|
||
PCHAR p;
|
||
ULONG length;
|
||
|
||
//
|
||
// The source string is not Unicode. Determine the length of
|
||
// the string by finding the zero terminator or the end of the
|
||
// input buffer. We need the length in order to convert the
|
||
// string to Unicode, and we can't just call RtlInitString, in
|
||
// case the string isn't terminated.
|
||
//
|
||
|
||
for ( p = Name, length = 0;
|
||
p <= LastValidLocation && *p != 0;
|
||
p++, length++ ) {
|
||
;
|
||
}
|
||
|
||
//
|
||
// Convert the source string to Unicode.
|
||
//
|
||
|
||
oemString.Buffer = Name;
|
||
oemString.Length = (USHORT)length;
|
||
oemString.MaximumLength = (USHORT)length;
|
||
|
||
status = RtlOemStringToUnicodeString(
|
||
String,
|
||
&oemString,
|
||
TRUE
|
||
);
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
return status;
|
||
}
|
||
|
||
name = (PWCH)String->Buffer;
|
||
LastValidLocation = (PCHAR)String->Buffer + String->Length;
|
||
|
||
}
|
||
|
||
//
|
||
// Though everything is done in place, separate source and
|
||
// destination pointers are maintained. It is necessary that source
|
||
// >= destination at all times to avoid writing into space we
|
||
// haven't looked at yet. The three main operations performed by
|
||
// this routine ( ".\", "..\", and getting rid of trailing "." and "
|
||
// ") do not interfere with this goal.
|
||
//
|
||
|
||
destination = name;
|
||
source = name;
|
||
|
||
//
|
||
// The lastComponent variable is used as a placeholder when
|
||
// backtracking over trailing blanks and dots. It points to the
|
||
// first character after the last directory separator or the
|
||
// beginning of the pathname.
|
||
//
|
||
|
||
lastComponent = destination;
|
||
|
||
//
|
||
// Get rid of leading directory separators.
|
||
//
|
||
|
||
while ( source <= (PWCH)LastValidLocation &&
|
||
(*source == UNICODE_DIR_SEPARATOR_CHAR) && (*source != L'\0') ) {
|
||
source++;
|
||
}
|
||
|
||
//
|
||
// Walk through the pathname until we reach the zero terminator. At
|
||
// the start of this loop, source points to the first charaecter
|
||
// after a directory separator or the first character of the
|
||
// pathname.
|
||
//
|
||
|
||
while ( (source <= (PWCH)LastValidLocation) && (*source != L'\0') ) {
|
||
|
||
if ( *source == L'.' ) {
|
||
|
||
//
|
||
// If we see a dot, look at the next character.
|
||
//
|
||
|
||
if ( notNtClient &&
|
||
((source+1) <= (PWCH)LastValidLocation) &&
|
||
(*(source+1) == UNICODE_DIR_SEPARATOR_CHAR) ) {
|
||
|
||
//
|
||
// If the next character is a directory separator,
|
||
// advance the source pointer to the directory
|
||
// separator.
|
||
//
|
||
|
||
source += 1;
|
||
|
||
} else if ( ((source+1) <= (PWCH)LastValidLocation) &&
|
||
(*(source+1) == L'.') &&
|
||
((source+1) == (PWCH)LastValidLocation ||
|
||
IS_UNICODE_PATH_SEPARATOR( *(source+2) ))) {
|
||
|
||
//
|
||
// If the following characters are ".\", we have a "..\".
|
||
// Advance the source pointer to the "\".
|
||
//
|
||
|
||
source += 2;
|
||
|
||
//
|
||
// Move the destination pointer to the charecter before the
|
||
// last directory separator in order to prepare for backing
|
||
// up. This may move the pointer before the beginning of
|
||
// the name pointer.
|
||
//
|
||
|
||
destination -= 2;
|
||
|
||
//
|
||
// If destination points before the beginning of the name
|
||
// pointer, fail because the user is attempting to go
|
||
// to a higher directory than the share root. This is
|
||
// the equivalent of a leading "..\", but may result from
|
||
// a case like "dir\..\..\file".
|
||
//
|
||
|
||
if ( destination <= name ) {
|
||
if ( !SourceIsUnicode ) {
|
||
RtlFreeUnicodeString( String );
|
||
String->Buffer = NULL;
|
||
}
|
||
return STATUS_OBJECT_PATH_SYNTAX_BAD;
|
||
}
|
||
|
||
//
|
||
// Back up the destination pointer to after the last
|
||
// directory separator or to the beginning of the pathname.
|
||
// Backup to the beginning of the pathname will occur
|
||
// in a case like "dir\..\file".
|
||
//
|
||
|
||
while ( destination >= name &&
|
||
*destination != UNICODE_DIR_SEPARATOR_CHAR ) {
|
||
destination--;
|
||
}
|
||
|
||
//
|
||
// destination points to \ or character before name; we
|
||
// want it to point to character after last \.
|
||
//
|
||
|
||
destination++;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The characters after the dot are not "\" or ".\", so
|
||
// so just copy source to destination until we reach a
|
||
// directory separator character. This will occur in
|
||
// a case like ".file" (filename starts with a dot).
|
||
//
|
||
|
||
do {
|
||
*destination++ = *source++;
|
||
} while ( (source <= (PWCH)LastValidLocation) &&
|
||
!IS_UNICODE_PATH_SEPARATOR( *source ) );
|
||
|
||
numberOfPathElements++;
|
||
|
||
}
|
||
|
||
} else { // if ( *source == L'.' )
|
||
|
||
// Try to parse out a snap token
|
||
if( SrvSnapParseToken( source, &WorkContext->SnapShotTime ) )
|
||
{
|
||
while ( (source <= (PWCH)LastValidLocation) &&
|
||
!IS_UNICODE_PATH_SEPARATOR( *source ) ) {
|
||
source++;
|
||
}
|
||
|
||
#if DBG
|
||
if( !(WorkContext->RequestHeader->Flags2 & SMB_FLAGS2_REPARSE_PATH) )
|
||
{
|
||
DbgPrint( "Found token but REPARSE not set!\n" );
|
||
DbgBreakPoint();
|
||
}
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
while ( (source <= (PWCH)LastValidLocation) &&
|
||
!IS_UNICODE_PATH_SEPARATOR( *source ) ) {
|
||
*destination++ = *source++;
|
||
}
|
||
}
|
||
|
||
numberOfPathElements++;
|
||
|
||
}
|
||
|
||
//
|
||
// Truncate trailing dots and blanks. destination should point
|
||
// to the last character before the directory separator, so back
|
||
// up over blanks and dots.
|
||
//
|
||
|
||
if ( notNtClient ) {
|
||
|
||
while ( ( destination > lastComponent ) &&
|
||
( (RemoveTrailingDots && *(destination-1) == '.')
|
||
|| *(destination-1) == ' ' ) ) {
|
||
destination--;
|
||
}
|
||
}
|
||
|
||
//
|
||
// At this point, source points to a directory separator or to
|
||
// a zero terminator. If it is a directory separator, put one
|
||
// in the destination.
|
||
//
|
||
|
||
if ( (source <= (PWCH)LastValidLocation) &&
|
||
(*source == UNICODE_DIR_SEPARATOR_CHAR) ) {
|
||
|
||
//
|
||
// If we haven't put the directory separator in the path name,
|
||
// put it in.
|
||
//
|
||
|
||
if ( destination != name &&
|
||
*(destination-1) != UNICODE_DIR_SEPARATOR_CHAR ) {
|
||
|
||
*destination++ = UNICODE_DIR_SEPARATOR_CHAR;
|
||
|
||
}
|
||
|
||
//
|
||
// It is legal to have multiple directory separators, so get
|
||
// rid of them here. Example: "dir\\\\\\\\file".
|
||
//
|
||
|
||
do {
|
||
source++;
|
||
} while ( (source <= (PWCH)LastValidLocation) &&
|
||
(*source == UNICODE_DIR_SEPARATOR_CHAR) );
|
||
|
||
//
|
||
// Make lastComponent point to the character after the directory
|
||
// separator.
|
||
//
|
||
|
||
lastComponent = destination;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// We're just about done. If there was a trailing .. (example:
|
||
// "file\.."), trailing . ("file\."), or multiple trailing
|
||
// separators ("file\\\\"), then back up one since separators are
|
||
// illegal at the end of a pathname.
|
||
//
|
||
|
||
if ( destination > name &&
|
||
*(destination-1) == UNICODE_DIR_SEPARATOR_CHAR ) {
|
||
|
||
destination--;
|
||
}
|
||
|
||
*destination = L'\0';
|
||
|
||
//
|
||
// The length of the destination string is the difference between the
|
||
// destination pointer (points to zero terminator at this point)
|
||
// and the name pointer (points to the beginning of the destination
|
||
// string).
|
||
//
|
||
|
||
String->Length = (SHORT)((PCHAR)destination - (PCHAR)name);
|
||
String->MaximumLength = String->Length;
|
||
|
||
//
|
||
// One final thing: Is this SMB referring to a DFS name? If so, ask
|
||
// the DFS driver to turn it into a local name.
|
||
//
|
||
if( ARGUMENT_PRESENT( Share ) &&
|
||
Share->IsDfs &&
|
||
SMB_CONTAINS_DFS_NAME( WorkContext )) {
|
||
|
||
BOOLEAN stripLastComponent = FALSE;
|
||
|
||
//
|
||
// We have to special case some SMBs (like TRANS2_FIND_FIRST2)
|
||
// because they contain path Dfs path names that could refer to a
|
||
// junction point. The SMB handlers for these SMBs are not interested
|
||
// in a STATUS_PATH_NOT_COVERED error; instead they want the name
|
||
// to be resolved to the the junction point.
|
||
//
|
||
|
||
if (WorkContext->NextCommand == SMB_COM_TRANSACTION2 ) {
|
||
|
||
PTRANSACTION transaction;
|
||
USHORT command;
|
||
|
||
transaction = WorkContext->Parameters.Transaction;
|
||
command = SmbGetUshort( &transaction->InSetup[0] );
|
||
|
||
if (command == TRANS2_FIND_FIRST2 && numberOfPathElements > 2 )
|
||
stripLastComponent = TRUE;
|
||
|
||
}
|
||
|
||
status =
|
||
DfsNormalizeName(Share, RelatedPath, stripLastComponent, String);
|
||
|
||
SMB_MARK_AS_DFS_TRANSLATED( WorkContext );
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
if ( !SourceIsUnicode ) {
|
||
RtlFreeUnicodeString( String );
|
||
String->Buffer = NULL;
|
||
}
|
||
}
|
||
}
|
||
|
||
#if DBG
|
||
if( (WorkContext->RequestHeader->Flags2 & SMB_FLAGS2_REPARSE_PATH) &&
|
||
(WorkContext->SnapShotTime.QuadPart == 0) )
|
||
{
|
||
DbgPrint( "Token not found but REPARSE set!\n" );
|
||
DbgBreakPoint();
|
||
}
|
||
#endif
|
||
|
||
return status;
|
||
|
||
} // SrvCanonicalizePathNameWithReparse
|
||
|
||
|
||
|
||
NTSTATUS
|
||
SrvCheckForSavedError(
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN PRFCB Rfcb
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine checks to see if there was a saved error.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Pointer to the workcontext block which will be
|
||
marked with the error.
|
||
|
||
Rfcb - Pointer to the rfcb which contains the saved error status.
|
||
|
||
Return Value:
|
||
|
||
status of SavedErrorCode.
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
NTSTATUS savedErrorStatus;
|
||
KIRQL oldIrql;
|
||
|
||
UNLOCKABLE_CODE( 8FIL );
|
||
|
||
//
|
||
// Acquire the spin lock and see if the saved error
|
||
// is still there.
|
||
//
|
||
|
||
ACQUIRE_SPIN_LOCK( &Rfcb->Connection->SpinLock, &oldIrql );
|
||
savedErrorStatus = Rfcb->SavedError;
|
||
if ( !NT_SUCCESS( savedErrorStatus ) ) {
|
||
|
||
//
|
||
// There was a write behind error. Fail this operation
|
||
// with the write error.
|
||
//
|
||
|
||
Rfcb->SavedError = STATUS_SUCCESS;
|
||
RELEASE_SPIN_LOCK( &Rfcb->Connection->SpinLock, oldIrql );
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvCheckForSavedError: Returning write"
|
||
"behind error %X\n", savedErrorStatus ));
|
||
}
|
||
SrvSetSmbError( WorkContext, savedErrorStatus );
|
||
|
||
} else {
|
||
|
||
RELEASE_SPIN_LOCK( &Rfcb->Connection->SpinLock, oldIrql );
|
||
}
|
||
|
||
return savedErrorStatus;
|
||
|
||
} // SrvCheckForSavedError
|
||
|
||
|
||
NTSTATUS SRVFASTCALL
|
||
SrvCheckSearchAttributes(
|
||
IN USHORT FileAttributes,
|
||
IN USHORT SmbSearchAttributes
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Determines whether the FileAttributes has
|
||
attributes not specified in SmbSearchAttributes. Only the system
|
||
and hidden bits are examined.
|
||
|
||
Arguments:
|
||
|
||
FileAttributes - The attributes in question
|
||
|
||
SmbSearchAttributes - the search attributes passed in an SMB.
|
||
|
||
Return Value:
|
||
|
||
STATUS_NO_SUCH_FILE if the attributes do not jive, or STATUS_SUCCESS if
|
||
the search attributes encompass the attributes on the file.
|
||
|
||
--*/
|
||
{
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// If the search attributes has both the system and hidden bits set,
|
||
// then the file must be OK.
|
||
//
|
||
|
||
if ( (SmbSearchAttributes & FILE_ATTRIBUTE_SYSTEM) != 0 &&
|
||
(SmbSearchAttributes & FILE_ATTRIBUTE_HIDDEN) != 0 ) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Mask out everything but the system and hidden bits--they're all
|
||
// we care about.
|
||
//
|
||
|
||
FileAttributes &= (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN);
|
||
|
||
//
|
||
// If a bit is set in fileAttributes that was not set in the search
|
||
// attributes, then their bitwise OR will have a bit set that is
|
||
// not set in search attributes.
|
||
//
|
||
|
||
if ( (SmbSearchAttributes | FileAttributes) != SmbSearchAttributes ) {
|
||
return STATUS_NO_SUCH_FILE;
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvCheckSearchAttributes
|
||
|
||
|
||
NTSTATUS
|
||
SrvCheckSearchAttributesForHandle(
|
||
IN HANDLE FileHandle,
|
||
IN USHORT SmbSearchAttributes
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Determines whether the file corresponding to FileHandle has
|
||
attributes not specified in SmbSearchAttributes. Only the system
|
||
and hidden bits are examined.
|
||
|
||
Arguments:
|
||
|
||
FileHandle - handle to the file; must have FILE_READ_ATTRIBUTES access.
|
||
|
||
SmbSearchAttributes - the search attributes passed in an SMB.
|
||
|
||
Return Value:
|
||
|
||
STATUS_NO_SUCH_FILE if the attributes do not jive, some other status
|
||
code if the NtQueryInformationFile fails, or STATUS_SUCCESS if
|
||
the search attributes encompass the attributes on the file.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
FILE_BASIC_INFORMATION fileBasicInformation;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// If the search attributes has both the system and hidden bits set,
|
||
// then the file must be OK.
|
||
//
|
||
|
||
if ( (SmbSearchAttributes & FILE_ATTRIBUTE_SYSTEM) != 0 &&
|
||
(SmbSearchAttributes & FILE_ATTRIBUTE_HIDDEN) != 0 ) {
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Get the attributes on the file.
|
||
//
|
||
|
||
status = SrvQueryBasicAndStandardInformation(
|
||
FileHandle,
|
||
NULL,
|
||
&fileBasicInformation,
|
||
NULL
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvCheckSearchAttributesForHandle: NtQueryInformationFile (basic "
|
||
"information) returned %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_QUERY_INFO_FILE, status );
|
||
return status;
|
||
}
|
||
|
||
return SrvCheckSearchAttributes( (USHORT)fileBasicInformation.FileAttributes,
|
||
SmbSearchAttributes );
|
||
|
||
} // SrvCheckSearchAttributesForHandle
|
||
|
||
VOID
|
||
SrvGetAlertServiceName(
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine gets the server display string from the registry.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
none.
|
||
|
||
--*/
|
||
{
|
||
UNICODE_STRING unicodeKeyName;
|
||
UNICODE_STRING unicodeRegPath;
|
||
OBJECT_ATTRIBUTES objAttributes;
|
||
HANDLE keyHandle;
|
||
|
||
ULONG lengthNeeded;
|
||
NTSTATUS status;
|
||
|
||
PWCHAR displayString;
|
||
PWCHAR newString;
|
||
|
||
PKEY_VALUE_FULL_INFORMATION infoBuffer = NULL;
|
||
|
||
PAGED_CODE( );
|
||
|
||
RtlInitUnicodeString( &unicodeRegPath, StrRegServerPath );
|
||
RtlInitUnicodeString( &unicodeKeyName, StrRegSrvDisplayName );
|
||
|
||
InitializeObjectAttributes(
|
||
&objAttributes,
|
||
&unicodeRegPath,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
NULL
|
||
);
|
||
|
||
status = ZwOpenKey(
|
||
&keyHandle,
|
||
KEY_QUERY_VALUE,
|
||
&objAttributes
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
goto use_default;
|
||
}
|
||
|
||
status = ZwQueryValueKey(
|
||
keyHandle,
|
||
&unicodeKeyName,
|
||
KeyValueFullInformation,
|
||
NULL,
|
||
0,
|
||
&lengthNeeded
|
||
);
|
||
|
||
if ( status != STATUS_BUFFER_TOO_SMALL ) {
|
||
NtClose( keyHandle );
|
||
goto use_default;
|
||
}
|
||
|
||
infoBuffer = ALLOCATE_HEAP_COLD( lengthNeeded, BlockTypeDataBuffer );
|
||
|
||
if ( infoBuffer == NULL ) {
|
||
NtClose( keyHandle );
|
||
goto use_default;
|
||
}
|
||
|
||
status = ZwQueryValueKey(
|
||
keyHandle,
|
||
&unicodeKeyName,
|
||
KeyValueFullInformation,
|
||
infoBuffer,
|
||
lengthNeeded,
|
||
&lengthNeeded
|
||
);
|
||
|
||
NtClose( keyHandle );
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
goto use_default;
|
||
}
|
||
|
||
//
|
||
// If it's empty, use the default.
|
||
//
|
||
|
||
lengthNeeded = infoBuffer->DataLength;
|
||
if ( lengthNeeded <= sizeof(WCHAR) ) {
|
||
goto use_default;
|
||
}
|
||
|
||
//
|
||
// Get the display string. If this is the same as the default,
|
||
// exit.
|
||
//
|
||
|
||
displayString = (PWCHAR)((PCHAR)infoBuffer + infoBuffer->DataOffset);
|
||
|
||
if ( wcscmp( displayString, StrDefaultSrvDisplayName ) == 0 ) {
|
||
goto use_default;
|
||
}
|
||
|
||
//
|
||
// allocate memory for the new display string
|
||
//
|
||
|
||
newString = (PWCHAR)ALLOCATE_HEAP_COLD( lengthNeeded, BlockTypeDataBuffer );
|
||
|
||
if ( newString == NULL ) {
|
||
goto use_default;
|
||
}
|
||
|
||
RtlCopyMemory(
|
||
newString,
|
||
displayString,
|
||
lengthNeeded
|
||
);
|
||
|
||
SrvAlertServiceName = newString;
|
||
FREE_HEAP( infoBuffer );
|
||
return;
|
||
|
||
use_default:
|
||
|
||
if ( infoBuffer != NULL ) {
|
||
FREE_HEAP( infoBuffer );
|
||
}
|
||
|
||
SrvAlertServiceName = StrDefaultSrvDisplayName;
|
||
return;
|
||
|
||
} // SrvGetAlertServiceName
|
||
|
||
VOID
|
||
SrvGetBaseFileName (
|
||
IN PUNICODE_STRING InputName,
|
||
OUT PUNICODE_STRING OutputName
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine finds the part of a path name that is only a file
|
||
name. For example, with a\b\c\filename, it sets the buffer
|
||
field of OutputName to point to "filename" and the length to 8.
|
||
|
||
*** This routine should be used AFTER SrvCanonicalizePathName has
|
||
been used on the path name to ensure that the name is good and
|
||
'..' have been removed.
|
||
|
||
Arguments:
|
||
|
||
InputName - Supplies a pointer to the pathname string.
|
||
|
||
OutputName - a pointer to where the base name information should
|
||
be written.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PWCH ep = &InputName->Buffer[ InputName->Length / sizeof(WCHAR) ];
|
||
PWCH baseFileName = ep - 1;
|
||
|
||
PAGED_CODE( );
|
||
|
||
for( ; baseFileName > InputName->Buffer; --baseFileName ) {
|
||
if( *baseFileName == DIRECTORY_SEPARATOR_CHAR ) {
|
||
OutputName->Buffer = baseFileName + 1;
|
||
OutputName->Length = PTR_DIFF_SHORT(ep, OutputName->Buffer);
|
||
OutputName->MaximumLength = OutputName->Length;
|
||
return;
|
||
}
|
||
}
|
||
|
||
*OutputName = *InputName;
|
||
|
||
return;
|
||
|
||
} // SrvGetBaseFileName
|
||
|
||
VOID
|
||
SrvGetMultiSZList(
|
||
PWSTR **ListPointer,
|
||
PWSTR BaseKeyName,
|
||
PWSTR ParameterKeyName,
|
||
PWSTR *DefaultList
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine queries a registry value key for its MULTI_SZ values.
|
||
|
||
Arguments:
|
||
|
||
ListPointer - Pointer to receive the pointer to the null session pipes.
|
||
ParameterKeyValue - Name of the value parameter to query.
|
||
DefaultList - Value to assign to the list pointer in case
|
||
something goes wrong.
|
||
|
||
Return Value:
|
||
|
||
none.
|
||
|
||
--*/
|
||
{
|
||
UNICODE_STRING unicodeKeyName;
|
||
UNICODE_STRING unicodeParamPath;
|
||
OBJECT_ATTRIBUTES objAttributes;
|
||
HANDLE keyHandle;
|
||
|
||
ULONG lengthNeeded;
|
||
ULONG i;
|
||
ULONG numberOfEntries;
|
||
ULONG numberOfDefaultEntries = 0;
|
||
NTSTATUS status;
|
||
|
||
PWCHAR regEntry;
|
||
PWCHAR dataEntry;
|
||
PWSTR *ptrEntry;
|
||
PCHAR newBuffer;
|
||
PKEY_VALUE_FULL_INFORMATION infoBuffer = NULL;
|
||
|
||
PAGED_CODE( );
|
||
|
||
RtlInitUnicodeString( &unicodeParamPath, BaseKeyName );
|
||
RtlInitUnicodeString( &unicodeKeyName, ParameterKeyName );
|
||
|
||
InitializeObjectAttributes(
|
||
&objAttributes,
|
||
&unicodeParamPath,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
NULL
|
||
);
|
||
|
||
status = ZwOpenKey(
|
||
&keyHandle,
|
||
KEY_QUERY_VALUE,
|
||
&objAttributes
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
goto use_default;
|
||
}
|
||
|
||
status = ZwQueryValueKey(
|
||
keyHandle,
|
||
&unicodeKeyName,
|
||
KeyValueFullInformation,
|
||
NULL,
|
||
0,
|
||
&lengthNeeded
|
||
);
|
||
|
||
if ( status != STATUS_BUFFER_TOO_SMALL ) {
|
||
NtClose( keyHandle );
|
||
goto use_default;
|
||
}
|
||
|
||
infoBuffer = ALLOCATE_HEAP_COLD( lengthNeeded, BlockTypeDataBuffer );
|
||
|
||
if ( infoBuffer == NULL ) {
|
||
NtClose( keyHandle );
|
||
goto use_default;
|
||
}
|
||
|
||
status = ZwQueryValueKey(
|
||
keyHandle,
|
||
&unicodeKeyName,
|
||
KeyValueFullInformation,
|
||
infoBuffer,
|
||
lengthNeeded,
|
||
&lengthNeeded
|
||
);
|
||
|
||
NtClose( keyHandle );
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
goto use_default;
|
||
}
|
||
|
||
//
|
||
// Figure out how many entries there are.
|
||
//
|
||
// numberOfEntries should be total number of entries + 1. The extra
|
||
// one is for the NULL sentinel entry.
|
||
//
|
||
|
||
lengthNeeded = infoBuffer->DataLength;
|
||
if ( lengthNeeded <= sizeof(WCHAR) ) {
|
||
|
||
//
|
||
// No entries on the list. Use default.
|
||
//
|
||
|
||
goto use_default;
|
||
}
|
||
|
||
dataEntry = (PWCHAR)((PCHAR)infoBuffer + infoBuffer->DataOffset);
|
||
for ( i = 0, regEntry = dataEntry, numberOfEntries = 0;
|
||
i < lengthNeeded;
|
||
i += sizeof(WCHAR) ) {
|
||
|
||
if ( *regEntry++ == L'\0' ) {
|
||
numberOfEntries++;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Add the number of entries in the default list.
|
||
//
|
||
|
||
if ( DefaultList != NULL ) {
|
||
for ( i = 0; DefaultList[i] != NULL ; i++ ) {
|
||
numberOfDefaultEntries++;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Allocate space needed for the array of pointers. This is in addition
|
||
// to the ones in the default list.
|
||
//
|
||
|
||
newBuffer = ALLOCATE_HEAP_COLD(
|
||
lengthNeeded +
|
||
(numberOfDefaultEntries + numberOfEntries + 1) *
|
||
sizeof( PWSTR ),
|
||
BlockTypeDataBuffer
|
||
);
|
||
|
||
if ( newBuffer == NULL ) {
|
||
goto use_default;
|
||
}
|
||
|
||
//
|
||
// Copy the names
|
||
//
|
||
|
||
regEntry = (PWCHAR)(newBuffer +
|
||
(numberOfDefaultEntries + numberOfEntries + 1) * sizeof(PWSTR));
|
||
|
||
RtlCopyMemory(
|
||
regEntry,
|
||
dataEntry,
|
||
lengthNeeded
|
||
);
|
||
|
||
//
|
||
// Free the info buffer
|
||
//
|
||
|
||
FREE_HEAP( infoBuffer );
|
||
|
||
//
|
||
// Copy the pointers in the default list.
|
||
//
|
||
|
||
ptrEntry = (PWSTR *) newBuffer;
|
||
|
||
for ( i = 0; i < numberOfDefaultEntries ; i++ ) {
|
||
|
||
*ptrEntry++ = DefaultList[i];
|
||
|
||
}
|
||
|
||
//
|
||
// Build the array of pointers. If numberOfEntries is 1, then
|
||
// it means that the list is empty.
|
||
//
|
||
|
||
|
||
if ( numberOfEntries > 1 ) {
|
||
|
||
*ptrEntry++ = regEntry++;
|
||
|
||
//
|
||
// Skip the first WCHAR and the last 2 NULL terminators.
|
||
//
|
||
|
||
for ( i = 3*sizeof(WCHAR) ; i < lengthNeeded ; i += sizeof(WCHAR) ) {
|
||
if ( *regEntry++ == L'\0' ) {
|
||
*ptrEntry++ = regEntry;
|
||
}
|
||
}
|
||
}
|
||
|
||
*ptrEntry = NULL;
|
||
*ListPointer = (PWSTR *)newBuffer;
|
||
return;
|
||
|
||
use_default:
|
||
|
||
if ( infoBuffer != NULL ) {
|
||
FREE_HEAP( infoBuffer );
|
||
}
|
||
*ListPointer = DefaultList;
|
||
return;
|
||
|
||
} // SrvGetMultiSZList
|
||
|
||
VOID
|
||
SrvGetOsVersionString(
|
||
VOID
|
||
)
|
||
{
|
||
|
||
ULONG lengthNeeded;
|
||
NTSTATUS status;
|
||
HANDLE keyHandle;
|
||
UNICODE_STRING unicodeParamPath;
|
||
UNICODE_STRING unicodeKeyName;
|
||
OBJECT_ATTRIBUTES objAttributes;
|
||
PKEY_VALUE_FULL_INFORMATION infoBuffer = NULL;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Read the version number string
|
||
//
|
||
|
||
RtlInitUnicodeString( &unicodeParamPath, StrRegOsVersionPath );
|
||
RtlInitUnicodeString( &unicodeKeyName, StrRegVersionKeyName );
|
||
|
||
InitializeObjectAttributes(
|
||
&objAttributes,
|
||
&unicodeParamPath,
|
||
OBJ_CASE_INSENSITIVE,
|
||
NULL,
|
||
NULL
|
||
);
|
||
|
||
status = ZwOpenKey(
|
||
&keyHandle,
|
||
KEY_QUERY_VALUE,
|
||
&objAttributes
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
goto use_default;
|
||
}
|
||
|
||
status = ZwQueryValueKey(
|
||
keyHandle,
|
||
&unicodeKeyName,
|
||
KeyValueFullInformation,
|
||
NULL,
|
||
0,
|
||
&lengthNeeded
|
||
);
|
||
|
||
if ( status != STATUS_BUFFER_TOO_SMALL ) {
|
||
NtClose( keyHandle );
|
||
goto use_default;
|
||
}
|
||
|
||
infoBuffer = ALLOCATE_HEAP_COLD( lengthNeeded, BlockTypeDataBuffer );
|
||
|
||
if ( infoBuffer == NULL ) {
|
||
NtClose( keyHandle );
|
||
goto use_default;
|
||
}
|
||
|
||
status = ZwQueryValueKey(
|
||
keyHandle,
|
||
&unicodeKeyName,
|
||
KeyValueFullInformation,
|
||
infoBuffer,
|
||
lengthNeeded,
|
||
&lengthNeeded
|
||
);
|
||
|
||
NtClose( keyHandle );
|
||
|
||
//
|
||
// Construct the version (oem and unicode) strings.
|
||
//
|
||
|
||
if ( infoBuffer->DataLength != 0 ) {
|
||
|
||
ULONG stringLength;
|
||
|
||
stringLength = infoBuffer->DataLength +
|
||
STRLEN(StrNativeOsPrefix) * sizeof(WCHAR);
|
||
|
||
SrvNativeOS.MaximumLength = (USHORT)stringLength;
|
||
SrvNativeOS.Length = SrvNativeOS.MaximumLength - sizeof(WCHAR);
|
||
|
||
SrvNativeOS.Buffer =
|
||
ALLOCATE_HEAP_COLD( SrvNativeOS.MaximumLength, BlockTypeDataBuffer );
|
||
|
||
if ( SrvNativeOS.Buffer == NULL) {
|
||
|
||
goto use_default;
|
||
}
|
||
|
||
RtlCopyMemory(
|
||
SrvNativeOS.Buffer,
|
||
StrNativeOsPrefix,
|
||
STRLEN(StrNativeOsPrefix) * sizeof(WCHAR)
|
||
);
|
||
|
||
RtlCopyMemory(
|
||
SrvNativeOS.Buffer + STRLEN(StrNativeOsPrefix),
|
||
(PCHAR)infoBuffer+infoBuffer->DataOffset,
|
||
infoBuffer->DataLength
|
||
);
|
||
|
||
FREE_HEAP( infoBuffer );
|
||
infoBuffer = NULL;
|
||
|
||
//
|
||
// Now get the oem version
|
||
//
|
||
|
||
status = RtlUnicodeStringToOemString(
|
||
&SrvOemNativeOS,
|
||
&SrvNativeOS,
|
||
TRUE
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
|
||
FREE_HEAP( SrvNativeOS.Buffer );
|
||
goto use_default;
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
use_default:
|
||
|
||
if ( infoBuffer != NULL ) {
|
||
FREE_HEAP( infoBuffer );
|
||
}
|
||
|
||
RtlInitUnicodeString( &SrvNativeOS, StrDefaultNativeOs );
|
||
RtlInitAnsiString( (PANSI_STRING)&SrvOemNativeOS, StrDefaultNativeOsOem );
|
||
|
||
return;
|
||
|
||
} // SrvGetOsVersionString
|
||
|
||
|
||
USHORT
|
||
SrvGetString (
|
||
IN OUT PUNICODE_STRING Destination,
|
||
IN PVOID Source,
|
||
IN PVOID EndOfSourceBuffer,
|
||
IN BOOLEAN SourceIsUnicode
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Reads a string out of an SMB buffer, and converts it to Unicode,
|
||
if necessary. This function is similar to SrvMakeUnicodeString,
|
||
except
|
||
|
||
(1) It always copies data from source to destination
|
||
(2) It assumes storage for destination has been preallocated. Its length
|
||
is Destination->MaximumLength
|
||
|
||
Arguments:
|
||
|
||
Destination - the resultant Unicode string.
|
||
|
||
Source - a zero-terminated input.
|
||
|
||
EndOfSourceBuffer - A pointer to the end of the SMB buffer. Used to
|
||
protect the server from accessing beyond the end of the SMB buffer,
|
||
if the format is invalid.
|
||
|
||
SourceIsUnicode - TRUE if the source is already Unicode.
|
||
|
||
Return Value:
|
||
|
||
Length - Length of input buffer (included the NUL terminator).
|
||
-1 if EndOfSourceBuffer is reached before the NUL terminator.
|
||
|
||
--*/
|
||
|
||
{
|
||
USHORT length;
|
||
|
||
PAGED_CODE( );
|
||
|
||
if ( SourceIsUnicode ) {
|
||
|
||
PWCH currentChar = Source;
|
||
|
||
ASSERT( ((ULONG_PTR)Source & 1) == 0 );
|
||
|
||
length = 0;
|
||
while ( currentChar < (PWCH)EndOfSourceBuffer &&
|
||
*currentChar != UNICODE_NULL ) {
|
||
currentChar++;
|
||
length += sizeof( WCHAR );
|
||
}
|
||
|
||
//
|
||
// If we hit the end of the SMB buffer without finding a NUL, this
|
||
// is a bad string. Return an error.
|
||
//
|
||
|
||
if ( currentChar >= (PWCH)EndOfSourceBuffer ) {
|
||
return (USHORT)-1;
|
||
}
|
||
|
||
//
|
||
// If we overran our storage buffer, this is a bad string. Return an error
|
||
//
|
||
if( length + sizeof( UNICODE_NULL ) > Destination->MaximumLength ) {
|
||
return (USHORT)-1;
|
||
}
|
||
|
||
//
|
||
// Copy the unicode data to the destination, including the NULL. Set length
|
||
// of Destination to the non-null string length.
|
||
//
|
||
Destination->Length = length;
|
||
|
||
//
|
||
// We didn't change this to RtlCopyMemory because it is possible that
|
||
// the source and destination can overlap. Copy the NULL as well
|
||
//
|
||
|
||
RtlMoveMemory( Destination->Buffer, Source, length );
|
||
|
||
} else {
|
||
|
||
PCHAR currentChar = Source;
|
||
OEM_STRING sourceString;
|
||
|
||
length = 0;
|
||
while ( currentChar <= (PCHAR)EndOfSourceBuffer &&
|
||
*currentChar != '\0' ) {
|
||
currentChar++;
|
||
length++;
|
||
}
|
||
|
||
//
|
||
// If we hit the end of the SMB buffer without finding a NUL, this
|
||
// is a bad string. Return an error.
|
||
//
|
||
|
||
if ( currentChar > (PCHAR)EndOfSourceBuffer ) {
|
||
return (USHORT)-1;
|
||
}
|
||
|
||
//
|
||
// If we overran our storage buffer, this is a bad string. Return an error
|
||
//
|
||
if( (USHORT)(length + 1)*sizeof(WCHAR) > Destination->MaximumLength ) {
|
||
return (USHORT)-1;
|
||
}
|
||
|
||
sourceString.Buffer = Source;
|
||
sourceString.Length = length;
|
||
|
||
//
|
||
// Convert the data to unicode.
|
||
//
|
||
|
||
Destination->Length = 0;
|
||
RtlOemStringToUnicodeString( Destination, &sourceString, FALSE );
|
||
|
||
//
|
||
// Increment 'length', to indicate that the NUL has been copied.
|
||
//
|
||
|
||
length++;
|
||
|
||
}
|
||
|
||
//
|
||
// Return the number of bytes copied from the source buffer.
|
||
//
|
||
|
||
return length;
|
||
|
||
} // SrvGetString
|
||
|
||
USHORT
|
||
SrvGetStringLength (
|
||
IN PVOID Source,
|
||
IN PVOID EndOfSourceBuffer,
|
||
IN BOOLEAN SourceIsUnicode,
|
||
IN BOOLEAN IncludeNullTerminator
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns the length of a string in an SMB buffer in bytes.
|
||
If the end of the buffer is encountered before the NUL terminator,
|
||
the function returns -1 as the length.
|
||
|
||
Arguments:
|
||
|
||
Source - a NUL-terminated input.
|
||
|
||
EndOfSourceBuffer - A pointer to the end of the SMB buffer. Used to
|
||
protect the server from accessing beyond the end of the SMB buffer,
|
||
if the format is invalid.
|
||
|
||
SourceIsUnicode - TRUE if the source is already Unicode.
|
||
|
||
IncludeNullTerminator - TRUE if the Length to be returned includes the
|
||
null terminator.
|
||
|
||
Return Value:
|
||
|
||
Length - Length of input buffer. -1 if EndOfSourceBuffer is reached
|
||
before the NUL terminator.
|
||
|
||
--*/
|
||
|
||
{
|
||
USHORT length;
|
||
|
||
PAGED_CODE( );
|
||
|
||
if ( IncludeNullTerminator) {
|
||
length = 1;
|
||
} else {
|
||
length = 0;
|
||
}
|
||
|
||
if ( SourceIsUnicode ) {
|
||
|
||
PWCH currentChar = (PWCH)Source;
|
||
|
||
ASSERT( ((ULONG_PTR)currentChar & 1) == 0 );
|
||
|
||
while ( currentChar < (PWCH)EndOfSourceBuffer &&
|
||
*currentChar != UNICODE_NULL ) {
|
||
currentChar++;
|
||
length++;
|
||
}
|
||
|
||
//
|
||
// If we hit the end of the SMB buffer without finding a NUL, this
|
||
// is a bad string. Return an error.
|
||
//
|
||
|
||
if ( currentChar >= (PWCH)EndOfSourceBuffer ) {
|
||
length = (USHORT)-1;
|
||
} else {
|
||
length = (USHORT)(length * sizeof(WCHAR));
|
||
}
|
||
|
||
} else {
|
||
|
||
PCHAR currentChar = Source;
|
||
|
||
while ( currentChar <= (PCHAR)EndOfSourceBuffer &&
|
||
*currentChar != '\0' ) {
|
||
currentChar++;
|
||
length++;
|
||
}
|
||
|
||
//
|
||
// If we hit the end of the SMB buffer without finding a NUL, this
|
||
// is a bad string. Return an error.
|
||
//
|
||
|
||
if ( currentChar > (PCHAR)EndOfSourceBuffer ) {
|
||
length = (USHORT)-1;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Return the length of the string.
|
||
//
|
||
|
||
return length;
|
||
|
||
} // SrvGetStringLength
|
||
|
||
|
||
USHORT
|
||
SrvGetSubdirectoryLength (
|
||
IN PUNICODE_STRING InputName
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine finds the length of "subdirectory" information in a
|
||
path name, that is, the parts of the path name that are not the
|
||
actual name of the file. For example, for a\b\c\filename, it
|
||
returns 5. This allows the calling routine to open the directory
|
||
containing the file or get a full pathname to a file after a search.
|
||
|
||
*** This routine should be used AFTER SrvCanonicalizePathName has
|
||
been used on the path name to ensure that the name is good and
|
||
'..' have been removed.
|
||
|
||
Arguments:
|
||
|
||
InputName - Supplies a pointer to the pathname string.
|
||
|
||
Return Value:
|
||
|
||
The number of bytes that contain directory information. If the
|
||
input is just a filename, then 0 is returned.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG i;
|
||
PWCH baseFileName = InputName->Buffer;
|
||
|
||
PAGED_CODE( );
|
||
|
||
for ( i = 0; i < InputName->Length / sizeof(WCHAR); i++ ) {
|
||
|
||
//
|
||
// If s points to a directory separator, set fileBaseName to
|
||
// the character after the separator.
|
||
//
|
||
|
||
if ( InputName->Buffer[i] == DIRECTORY_SEPARATOR_CHAR ) {
|
||
baseFileName = &InputName->Buffer[i];
|
||
}
|
||
|
||
}
|
||
|
||
return (USHORT)((baseFileName - InputName->Buffer) * sizeof(WCHAR));
|
||
|
||
} // SrvGetSubdirectoryLength
|
||
|
||
|
||
BOOLEAN SRVFASTCALL
|
||
SrvIsLegalFatName (
|
||
IN PWSTR InputName,
|
||
IN CLONG InputNameLength
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Determines whether a file name would be legal for FAT. This
|
||
is needed for SrvQueryDirectoryFile because it must filter names
|
||
for clients that do not know about long or non-FAT filenames.
|
||
|
||
Arguments:
|
||
|
||
InputName - Supplies the string to test
|
||
InputNameLength - Length of the string (excluding the NULL termination)
|
||
to test.
|
||
|
||
Return Value:
|
||
|
||
TRUE if the name is a legal FAT name, FALSE if the name would be
|
||
rejected by FAT.
|
||
|
||
--*/
|
||
|
||
{
|
||
UNICODE_STRING original_name;
|
||
|
||
UNICODE_STRING upcase_name;
|
||
WCHAR upcase_buffer[ 13 ];
|
||
|
||
STRING oem_string;
|
||
CHAR oem_buffer[ 13 ];
|
||
|
||
UNICODE_STRING converted_name;
|
||
WCHAR converted_name_buffer[ 13 ];
|
||
|
||
BOOLEAN spacesInName, nameValid8Dot3;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// Special case . and .. -- they are legal FAT names
|
||
//
|
||
if( InputName[0] == L'.' ) {
|
||
|
||
if( InputNameLength == sizeof(WCHAR) ||
|
||
((InputNameLength == 2*sizeof(WCHAR)) && InputName[1] == L'.')) {
|
||
return TRUE;
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
original_name.Buffer = InputName;
|
||
original_name.Length = original_name.MaximumLength = (USHORT)InputNameLength;
|
||
|
||
nameValid8Dot3 = RtlIsNameLegalDOS8Dot3( &original_name, NULL, &spacesInName );
|
||
|
||
if( !nameValid8Dot3 || spacesInName ) {
|
||
return FALSE;
|
||
}
|
||
|
||
if( SrvFilterExtendedCharsInPath == FALSE ) {
|
||
//
|
||
// One final test -- we must be able to convert this name to OEM and back again
|
||
// without any loss of information.
|
||
//
|
||
|
||
oem_string.Buffer = oem_buffer;
|
||
upcase_name.Buffer = upcase_buffer;
|
||
converted_name.Buffer = converted_name_buffer;
|
||
|
||
oem_string.MaximumLength = sizeof( oem_buffer );
|
||
upcase_name.MaximumLength = sizeof( upcase_buffer );
|
||
converted_name.MaximumLength = sizeof( converted_name_buffer );
|
||
|
||
oem_string.Length = 0;
|
||
upcase_name.Length = 0;
|
||
converted_name.Length = 0;
|
||
|
||
nameValid8Dot3 = NT_SUCCESS( RtlUpcaseUnicodeString( &upcase_name, &original_name, FALSE )) &&
|
||
NT_SUCCESS( RtlUnicodeStringToOemString( &oem_string, &upcase_name, FALSE )) &&
|
||
FsRtlIsFatDbcsLegal( oem_string, FALSE, FALSE, FALSE ) &&
|
||
NT_SUCCESS( RtlOemStringToUnicodeString( &converted_name, &oem_string, FALSE )) &&
|
||
RtlEqualUnicodeString( &upcase_name, &converted_name, FALSE );
|
||
}
|
||
|
||
return nameValid8Dot3;
|
||
|
||
} // SrvIsLegalFatName
|
||
|
||
NTSTATUS
|
||
SrvMakeUnicodeString (
|
||
IN BOOLEAN SourceIsUnicode,
|
||
OUT PUNICODE_STRING Destination,
|
||
IN PVOID Source,
|
||
IN PUSHORT SourceLength OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Makes a unicode string from a zero-terminated input that is either
|
||
ANSI or Unicode.
|
||
|
||
Arguments:
|
||
|
||
SourceIsUnicode - TRUE if the source is already Unicode. If FALSE,
|
||
RtlOemStringToUnicodeString will allocate space to hold the
|
||
Unicode string; it is the responsibility of the caller to
|
||
free this space.
|
||
|
||
Destination - the resultant Unicode string.
|
||
|
||
Source - a zero-terminated input.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - result of operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
OEM_STRING oemString;
|
||
|
||
PAGED_CODE( );
|
||
|
||
if ( SourceIsUnicode ) {
|
||
|
||
ASSERT( ((ULONG_PTR)Source & 1) == 0 );
|
||
|
||
if ( ARGUMENT_PRESENT( SourceLength ) ) {
|
||
ASSERT( (*SourceLength) != (USHORT) -1 );
|
||
Destination->Buffer = Source;
|
||
Destination->Length = *SourceLength;
|
||
Destination->MaximumLength = *SourceLength;
|
||
} else {
|
||
RtlInitUnicodeString( Destination, Source );
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
if ( ARGUMENT_PRESENT( SourceLength ) ) {
|
||
oemString.Buffer = Source;
|
||
oemString.Length = *SourceLength;
|
||
oemString.MaximumLength = *SourceLength;
|
||
} else {
|
||
RtlInitString( &oemString, Source );
|
||
}
|
||
|
||
return RtlOemStringToUnicodeString(
|
||
Destination,
|
||
&oemString,
|
||
TRUE
|
||
);
|
||
|
||
} // SrvMakeUnicodeString
|
||
|
||
|
||
VOID
|
||
SrvReleaseContext (
|
||
IN PWORK_CONTEXT WorkContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function releases (dereferences) control blocks referenced by a
|
||
Work Context block. It is called when processing of an incoming SMB
|
||
is complete, just before the response SMB (if any) is set.
|
||
|
||
The following control blocks are dereferenced: Share, Session,
|
||
TreeConnect, and File. If any of these fields is nonzero in
|
||
WorkContext, the block is dereferenced and the fields is zeroed.
|
||
|
||
Note that the Connection block and the Endpoint block are NOT
|
||
dereferenced. This is based on the assumption that a response is
|
||
about to be sent, so the connection must stay referenced. The
|
||
Connection block is dereferenced after the send of the response (if
|
||
any) when SrvRequeueReceiveIrp is called. That function also
|
||
releases the response buffer, if it is different from the request
|
||
buffer.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to a work context block.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// !!! If you change the way this routine works (e.g., you add
|
||
// another block that needs to be dereferenced), make sure you
|
||
// check fsd.c\SrvFsdRestartSmbComplete to see if it needs to be
|
||
// changed too.
|
||
//
|
||
|
||
//
|
||
// Dereference the Share block, if any.
|
||
//
|
||
|
||
if ( WorkContext->Share != NULL ) {
|
||
SrvDereferenceShare( WorkContext->Share );
|
||
WorkContext->Share = NULL;
|
||
}
|
||
|
||
//
|
||
// Dereference the Session block, if any.
|
||
//
|
||
|
||
if ( WorkContext->Session != NULL ) {
|
||
SrvDereferenceSession( WorkContext->Session );
|
||
WorkContext->Session= NULL;
|
||
}
|
||
|
||
//
|
||
// Dereference the Tree Connect block, if any.
|
||
//
|
||
|
||
if ( WorkContext->TreeConnect != NULL ) {
|
||
SrvDereferenceTreeConnect( WorkContext->TreeConnect );
|
||
WorkContext->TreeConnect = NULL;
|
||
}
|
||
|
||
//
|
||
// Dereference the RFCB, if any.
|
||
//
|
||
|
||
if ( WorkContext->Rfcb != NULL ) {
|
||
SrvDereferenceRfcb( WorkContext->Rfcb );
|
||
WorkContext->OplockOpen = FALSE;
|
||
WorkContext->Rfcb = NULL;
|
||
}
|
||
|
||
//
|
||
// Dereference the wait for oplock break, if any
|
||
//
|
||
|
||
if ( WorkContext->WaitForOplockBreak != NULL ) {
|
||
SrvDereferenceWaitForOplockBreak( WorkContext->WaitForOplockBreak );
|
||
WorkContext->WaitForOplockBreak = NULL;
|
||
}
|
||
|
||
//
|
||
// If this was a blocking operation, update the blocking i/o count.
|
||
//
|
||
|
||
if ( WorkContext->BlockingOperation ) {
|
||
InterlockedDecrement( &SrvBlockingOpsInProgress );
|
||
WorkContext->BlockingOperation = FALSE;
|
||
}
|
||
|
||
return;
|
||
|
||
} // SrvReleaseContext
|
||
|
||
|
||
BOOLEAN
|
||
SrvSetFileWritethroughMode (
|
||
IN PLFCB Lfcb,
|
||
IN BOOLEAN Writethrough
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Sets the writethrough mode of a file as specified. Returns the
|
||
original mode of the file.
|
||
|
||
Arguments:
|
||
|
||
Lfcb - A pointer to the LFCB representing the open file.
|
||
|
||
Writethrough - A boolean indicating whether the file is to be placed
|
||
into writethrough mode (TRUE) or writebehind mode (FALSE).
|
||
|
||
Return Value:
|
||
|
||
BOOLEAN - Returns the original mode of the file.
|
||
|
||
--*/
|
||
|
||
{
|
||
FILE_MODE_INFORMATION modeInformation;
|
||
IO_STATUS_BLOCK iosb;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// If the file is already in the correct mode, simply return.
|
||
// Otherwise, set the file to the correct mode.
|
||
//
|
||
|
||
if ( Writethrough ) {
|
||
|
||
if ( (Lfcb->FileMode & FILE_WRITE_THROUGH) != 0 ) {
|
||
return TRUE;
|
||
} else {
|
||
Lfcb->FileMode |= FILE_WRITE_THROUGH;
|
||
}
|
||
|
||
} else {
|
||
|
||
if ( (Lfcb->FileMode & FILE_WRITE_THROUGH) == 0 ) {
|
||
return FALSE;
|
||
} else {
|
||
Lfcb->FileMode &= ~FILE_WRITE_THROUGH;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Change the file mode.
|
||
//
|
||
// !!! Don't do this by file handle -- build and issue an IRP using
|
||
// the file object pointer directly.
|
||
//
|
||
|
||
modeInformation.Mode = Lfcb->FileMode;
|
||
|
||
(VOID)NtSetInformationFile(
|
||
Lfcb->FileHandle,
|
||
&iosb,
|
||
&modeInformation,
|
||
sizeof( modeInformation ),
|
||
FileModeInformation
|
||
);
|
||
|
||
//
|
||
// Return the original mode of the file, which was the opposite of
|
||
// what was requested.
|
||
//
|
||
|
||
return (BOOLEAN)!Writethrough;
|
||
|
||
} // SrvSetFileWritethroughMode
|
||
|
||
VOID
|
||
SrvOemStringTo8dot3 (
|
||
IN POEM_STRING InputString,
|
||
OUT PSZ Output8dot3
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Convert a string into FAT 8.3 format. This derived from GaryKi's
|
||
routine FatStringTo8dot3 in fastfat\namesup.c.
|
||
|
||
Arguments:
|
||
|
||
InputString - Supplies the input string to convert
|
||
|
||
Output8dot3 - Receives the converted string. The memory must be
|
||
supplied by the caller.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
{
|
||
CLONG i, j;
|
||
PCHAR inBuffer = InputString->Buffer;
|
||
ULONG inLength = InputString->Length;
|
||
|
||
PAGED_CODE( );
|
||
|
||
ASSERT( inLength <= 12 );
|
||
|
||
//
|
||
// First make the output name all blanks.
|
||
//
|
||
|
||
RtlFillMemory( Output8dot3, 11, CHAR_SP );
|
||
|
||
//
|
||
// If we get "." or "..", just return them. They do not follow
|
||
// the usual rules for FAT names.
|
||
//
|
||
|
||
if( inBuffer[0] == '.' ) {
|
||
if( inLength == 1 ) {
|
||
Output8dot3[0] = '.';
|
||
return;
|
||
}
|
||
|
||
if( inLength == 2 && inBuffer[1] == '.' ) {
|
||
Output8dot3[0] = '.';
|
||
Output8dot3[1] = '.';
|
||
return;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Copy over the first part of the file name. Stop when we get to
|
||
// the end of the input string or a dot.
|
||
//
|
||
|
||
if (NLS_MB_CODE_PAGE_TAG) {
|
||
|
||
for ( i = 0;
|
||
(i < inLength) && (inBuffer[i] != '.') && (inBuffer[i] != '\\');
|
||
i++ ) {
|
||
|
||
if (FsRtlIsLeadDbcsCharacter(inBuffer[i])) {
|
||
|
||
if (i+1 < inLength) {
|
||
Output8dot3[i] = inBuffer[i];
|
||
i++;
|
||
Output8dot3[i] = inBuffer[i];
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
} else {
|
||
|
||
Output8dot3[i] = inBuffer[i];
|
||
}
|
||
}
|
||
|
||
} else {
|
||
|
||
for ( i = 0;
|
||
(i < inLength) && (inBuffer[i] != '.') && (inBuffer[i] != '\\');
|
||
i++ ) {
|
||
|
||
Output8dot3[i] = inBuffer[i];
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// See if we need to add an extension.
|
||
//
|
||
|
||
if ( i < inLength ) {
|
||
|
||
//
|
||
// Skip over the dot.
|
||
//
|
||
|
||
ASSERT( (inLength - i) <= 4 );
|
||
ASSERT( inBuffer[i] == '.' );
|
||
|
||
i++;
|
||
|
||
//
|
||
// Add the extension to the output name
|
||
//
|
||
|
||
if (NLS_MB_CODE_PAGE_TAG) {
|
||
|
||
for ( j = 8;
|
||
(i < inLength) && (inBuffer[i] != '\\');
|
||
i++, j++ ) {
|
||
|
||
if (FsRtlIsLeadDbcsCharacter(inBuffer[i])) {
|
||
|
||
if (i+1 < inLength) {
|
||
Output8dot3[j] = inBuffer[i];
|
||
i++; j++;
|
||
Output8dot3[j] = inBuffer[i];
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
} else {
|
||
|
||
Output8dot3[j] = inBuffer[i];
|
||
|
||
}
|
||
}
|
||
|
||
} else {
|
||
|
||
for ( j = 8;
|
||
(i < inLength) && (inBuffer[i] != '\\');
|
||
i++, j++ ) {
|
||
|
||
Output8dot3[j] = inBuffer[i];
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// We're all done with the conversion.
|
||
//
|
||
|
||
return;
|
||
|
||
} // SrvOemStringTo8dot3
|
||
|
||
|
||
VOID
|
||
SrvUnicodeStringTo8dot3 (
|
||
IN PUNICODE_STRING InputString,
|
||
OUT PSZ Output8dot3,
|
||
IN BOOLEAN Upcase
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Convert a string into fat 8.3 format. This derived from GaryKi's
|
||
routine FatStringTo8dot3 in fat\fatname.c.
|
||
|
||
Arguments:
|
||
|
||
InputString - Supplies the input string to convert
|
||
|
||
Output8dot3 - Receives the converted string, the memory must be supplied
|
||
by the caller.
|
||
|
||
Upcase - Whether the string is to be uppercased.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
ULONG oemSize;
|
||
OEM_STRING oemString;
|
||
ULONG index = 0;
|
||
UCHAR aSmallBuffer[ 50 ];
|
||
NTSTATUS status;
|
||
|
||
PAGED_CODE( );
|
||
|
||
oemSize = RtlUnicodeStringToOemSize( InputString );
|
||
|
||
ASSERT( oemSize < MAXUSHORT );
|
||
|
||
if( oemSize <= sizeof( aSmallBuffer ) ) {
|
||
oemString.Buffer = aSmallBuffer;
|
||
} else {
|
||
oemString.Buffer = ALLOCATE_HEAP( oemSize, BlockTypeBuffer );
|
||
if( oemString.Buffer == NULL ) {
|
||
*Output8dot3 = '\0';
|
||
return;
|
||
}
|
||
}
|
||
|
||
oemString.MaximumLength = (USHORT)oemSize;
|
||
oemString.Length = (USHORT)oemSize - 1;
|
||
|
||
if ( Upcase ) {
|
||
|
||
status = RtlUpcaseUnicodeToOemN(
|
||
oemString.Buffer,
|
||
oemString.Length,
|
||
&index,
|
||
InputString->Buffer,
|
||
InputString->Length
|
||
);
|
||
|
||
ASSERT( NT_SUCCESS( status ) );
|
||
|
||
|
||
} else {
|
||
|
||
status = RtlUnicodeToOemN(
|
||
oemString.Buffer,
|
||
oemString.Length,
|
||
&index,
|
||
InputString->Buffer,
|
||
InputString->Length
|
||
);
|
||
|
||
ASSERT( NT_SUCCESS( status ) );
|
||
}
|
||
|
||
if( NT_SUCCESS( status ) ) {
|
||
|
||
oemString.Buffer[ index ] = '\0';
|
||
|
||
SrvOemStringTo8dot3(
|
||
&oemString,
|
||
Output8dot3
|
||
);
|
||
} else {
|
||
|
||
*Output8dot3 = '\0';
|
||
}
|
||
|
||
if( oemSize > sizeof( aSmallBuffer ) ) {
|
||
FREE_HEAP( oemString.Buffer );
|
||
}
|
||
|
||
} // SrvUnicodeStringTo8dot3
|
||
|
||
#if SRVDBG_STATS
|
||
VOID SRVFASTCALL
|
||
SrvUpdateStatistics2 (
|
||
PWORK_CONTEXT WorkContext,
|
||
UCHAR SmbCommand
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Update the server statistics database to reflect the work item
|
||
that is being completed.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - pointer to the work item containing the statistics
|
||
for this request.
|
||
|
||
SmbCommand - The SMB command code of the current operation.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
if ( WorkContext->StartTime != 0 ) {
|
||
|
||
LARGE_INTEGER td;
|
||
|
||
td.QuadPart = WorkContext->CurrentWorkQueue->stats.SystemTime - WorkContext->StartTime;
|
||
|
||
//
|
||
// Update the SMB-specific statistics fields.
|
||
//
|
||
// !!! doesn't work for original transact smb--SmbCommand is too
|
||
// large.
|
||
|
||
//ASSERT( SmbCommand <= MAX_STATISTICS_SMB );
|
||
|
||
if ( SmbCommand <= MAX_STATISTICS_SMB ) {
|
||
SrvDbgStatistics.Smb[SmbCommand].SmbCount++;
|
||
SrvDbgStatistics.Smb[SmbCommand].TotalTurnaroundTime.QuadPart +=
|
||
td.QuadPart;
|
||
}
|
||
|
||
#if 0 // this code is no longer valid!
|
||
//
|
||
// Update the size-dependent IO fields if necessary. The arrays
|
||
// in SrvStatistics correspond to powers of two sizes for the IO.
|
||
// The correspondence between array location and IO size is:
|
||
//
|
||
// Location IO Size (min)
|
||
// 0 0
|
||
// 1 1
|
||
// 2 2
|
||
// 3 4
|
||
// 4 8
|
||
// 5 16
|
||
// 6 32
|
||
// 7 64
|
||
// 8 128
|
||
// 9 256
|
||
// 10 512
|
||
// 11 1024
|
||
// 12 2048
|
||
// 13 4096
|
||
// 14 8192
|
||
// 15 16384
|
||
// 16 32768
|
||
//
|
||
|
||
if ( WorkContext->BytesRead != 0 ) {
|
||
|
||
CLONG i;
|
||
|
||
for ( i = 0;
|
||
i < 17 && WorkContext->BytesRead != 0;
|
||
i++, WorkContext->BytesRead >>= 1 );
|
||
|
||
SrvDbgStatistics.ReadSize[i].SmbCount++;
|
||
SrvDbgStatistics.ReadSize[i].TotalTurnaroundTime.QuadPart +=
|
||
td.QuadPart;
|
||
}
|
||
|
||
if ( WorkContext->BytesWritten != 0 ) {
|
||
|
||
CLONG i;
|
||
|
||
for ( i = 0;
|
||
i < 17 && WorkContext->BytesWritten != 0;
|
||
i++, WorkContext->BytesWritten >>= 1 );
|
||
|
||
SrvDbgStatistics.WriteSize[i].SmbCount++;
|
||
SrvDbgStatistics.WriteSize[i].TotalTurnaroundTime.QuadPart +=
|
||
td.QuadPart;
|
||
}
|
||
#endif
|
||
|
||
}
|
||
|
||
return;
|
||
|
||
} // SrvUpdateStatistics2
|
||
#endif // SRVDBG_STATS
|
||
|
||
|
||
PRFCB
|
||
SrvVerifyFid2 (
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN USHORT Fid,
|
||
IN BOOLEAN FailOnSavedError,
|
||
IN PRESTART_ROUTINE SerializeWithRawRestartRoutine OPTIONAL,
|
||
OUT PNTSTATUS NtStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Verifies the FID, TID, and UID in an incoming SMB. If they are
|
||
valid, the address of the RFCB corresponding to the FID is returned,
|
||
and the block is referenced.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to the work context block for the
|
||
current SMB. In particular, the Connection block pointer is
|
||
used to find the appropriate file table. If the FID is valid,
|
||
the RFCB address is stored in WorkContext->Rfcb.
|
||
|
||
Fid - Supplies the FID sent in the request SMB
|
||
|
||
FailOnSavedError - If TRUE, return NULL to the caller if there is
|
||
an outstanding write behind error. If FALSE, always attempt
|
||
to return a pointer to the RFCB.
|
||
|
||
SerializeWithRawRestartRoutine - If not NULL, is the address of an
|
||
FSP restart routine, and specifies that this operation should be
|
||
queued if a raw write is currently in progress on the file. If
|
||
this is the case, this routine queues the work context block to
|
||
a queue in the RFCB. When the raw write completes, all work
|
||
items on the queue are restarted.
|
||
|
||
NtStatus - This field is filled in only when this function returns
|
||
NULL. If there was a write behind error, NtStatus returns the
|
||
write behind error status, otherwise it returns
|
||
STATUS_INVALID_HANDLE.
|
||
|
||
Return Value:
|
||
|
||
PRFCB - Address of the RFCB, or SRV_INVALID_RFCB_POINTER if the Fid
|
||
was invalid, or if there is a raw write in progress and
|
||
serialization was requested (in which case *NtStatus is set
|
||
to STATUS_SUCCESS).
|
||
|
||
--*/
|
||
|
||
{
|
||
PCONNECTION connection;
|
||
PTABLE_HEADER tableHeader;
|
||
PRFCB rfcb;
|
||
USHORT index;
|
||
USHORT sequence;
|
||
KIRQL oldIrql;
|
||
|
||
#if 0
|
||
// THIS IS NOW DONE IN THE SrvVerifyFid MACRO.
|
||
//
|
||
// If the FID has already been verified, return the RFCB pointer.
|
||
//
|
||
// *** Note that we don't do the saved error checking or the raw
|
||
// write serialization in this case, on the assumption that
|
||
// since we already passed the checks once (in order to get the
|
||
// RFCB pointer in the first place), we don't need to do them
|
||
// again.
|
||
//
|
||
|
||
if ( WorkContext->Rfcb != NULL ) {
|
||
return WorkContext->Rfcb;
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// Initialize local variables: obtain the connection block address
|
||
// and crack the FID into its components.
|
||
//
|
||
|
||
connection = WorkContext->Connection;
|
||
|
||
//
|
||
// Acquire the spin lock that guards the connection's file table.
|
||
//
|
||
|
||
ACQUIRE_SPIN_LOCK( &connection->SpinLock, &oldIrql );
|
||
|
||
//
|
||
// See if this is the cached rfcb
|
||
//
|
||
|
||
if ( connection->CachedFid == (ULONG)Fid ) {
|
||
|
||
rfcb = connection->CachedRfcb;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Verify that the FID is in range, is in use, and has the correct
|
||
// sequence number.
|
||
|
||
index = FID_INDEX( Fid );
|
||
sequence = FID_SEQUENCE( Fid );
|
||
tableHeader = &connection->FileTable;
|
||
|
||
if ( (index >= tableHeader->TableSize) ||
|
||
(tableHeader->Table[index].Owner == NULL) ||
|
||
(tableHeader->Table[index].SequenceNumber != sequence) ) {
|
||
|
||
*NtStatus = STATUS_INVALID_HANDLE;
|
||
goto error_exit;
|
||
}
|
||
|
||
rfcb = tableHeader->Table[index].Owner;
|
||
|
||
if ( GET_BLOCK_STATE(rfcb) != BlockStateActive ) {
|
||
|
||
*NtStatus = STATUS_INVALID_HANDLE;
|
||
goto error_exit;
|
||
}
|
||
|
||
//
|
||
// If the caller wants to fail when there is a write behind
|
||
// error and the error exists, fill in NtStatus and do not
|
||
// return the RFCB pointer.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(rfcb->SavedError) && FailOnSavedError ) {
|
||
|
||
if ( !NT_SUCCESS(rfcb->SavedError) ) {
|
||
*NtStatus = rfcb->SavedError;
|
||
rfcb->SavedError = STATUS_SUCCESS;
|
||
goto error_exit;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Cache the fid.
|
||
//
|
||
|
||
connection->CachedRfcb = rfcb;
|
||
connection->CachedFid = (ULONG)Fid;
|
||
}
|
||
|
||
//
|
||
// The FID is valid within the context of this connection. Verify
|
||
// that the owning tree connect's TID is correct.
|
||
//
|
||
// Do not verify the UID for clients that do not understand it.
|
||
//
|
||
|
||
if ( (rfcb->Tid !=
|
||
SmbGetAlignedUshort( &WorkContext->RequestHeader->Tid )) ||
|
||
((rfcb->Uid !=
|
||
SmbGetAlignedUshort( &WorkContext->RequestHeader->Uid )) &&
|
||
DIALECT_HONORS_UID(connection->SmbDialect)) ) {
|
||
|
||
*NtStatus = STATUS_INVALID_HANDLE;
|
||
goto error_exit;
|
||
}
|
||
|
||
//
|
||
// If raw write serialization was requested, and a raw write
|
||
// is active, queue this work item in the RFCB pending
|
||
// completion of the raw write.
|
||
//
|
||
|
||
if ( (rfcb->RawWriteCount != 0) &&
|
||
ARGUMENT_PRESENT(SerializeWithRawRestartRoutine) ) {
|
||
|
||
InsertTailList(
|
||
&rfcb->RawWriteSerializationList,
|
||
&WorkContext->ListEntry
|
||
);
|
||
|
||
WorkContext->FspRestartRoutine = SerializeWithRawRestartRoutine;
|
||
*NtStatus = STATUS_SUCCESS;
|
||
goto error_exit;
|
||
}
|
||
|
||
//
|
||
// The file is active and the TID is valid. Reference the
|
||
// RFCB. Release the spin lock (we don't need it anymore).
|
||
//
|
||
|
||
rfcb->BlockHeader.ReferenceCount++;
|
||
UPDATE_REFERENCE_HISTORY( rfcb, FALSE );
|
||
|
||
RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql );
|
||
|
||
//
|
||
// Save the RFCB address in the work context block and
|
||
// return the file address.
|
||
//
|
||
|
||
WorkContext->Rfcb = rfcb;
|
||
|
||
//
|
||
// Mark the rfcb as active
|
||
//
|
||
|
||
rfcb->IsActive = TRUE;
|
||
|
||
ASSERT( GET_BLOCK_TYPE( rfcb->Mfcb ) == BlockTypeMfcb );
|
||
|
||
return rfcb;
|
||
|
||
error_exit:
|
||
|
||
//
|
||
// Either the FID is invalid for this connection, the file is
|
||
// closing, or the TID doesn't match. Release the lock, clear the
|
||
// file address in the work context block, and return a file address
|
||
// of NULL.
|
||
//
|
||
|
||
WorkContext->Rfcb = NULL; // connection spinlock must be held
|
||
RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql );
|
||
|
||
return SRV_INVALID_RFCB_POINTER;
|
||
|
||
} // SrvVerifyFid2
|
||
|
||
|
||
PRFCB
|
||
SrvVerifyFidForRawWrite (
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN USHORT Fid,
|
||
OUT PNTSTATUS NtStatus
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Verifies the FID, TID, and UID in an incoming SMB. If they are
|
||
valid, the address of the RFCB corresponding to the FID is returned,
|
||
and the block is referenced. In addition, the RawWriteCount in the
|
||
RFCB is incremented.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to the work context block for the
|
||
current SMB. In particular, the Connection block pointer is
|
||
used to find the appropriate file table. If the FID is valid,
|
||
the RFCB address is stored in WorkContext->Rfcb.
|
||
|
||
Fid - Supplies the FID sent in the request SMB
|
||
|
||
NtStatus - This field is filled in only when this function returns
|
||
NULL. If there was a write behind error, NtStatus returns the
|
||
write behind error status, otherwise it returns
|
||
STATUS_INVALID_HANDLE.
|
||
|
||
Return Value:
|
||
|
||
PRFCB - Address of the RFCB, or SRV_INVALID_RFCB_POINTER if the Fid
|
||
was invalid, or if there is a raw write in progress and
|
||
serialization was requested (in which case *NtStatus is set
|
||
to STATUS_SUCCESS).
|
||
|
||
--*/
|
||
|
||
{
|
||
PCONNECTION connection;
|
||
PTABLE_HEADER tableHeader;
|
||
PRFCB rfcb;
|
||
USHORT index;
|
||
USHORT sequence;
|
||
KIRQL oldIrql;
|
||
|
||
ASSERT( WorkContext->Rfcb == NULL );
|
||
|
||
//
|
||
// Initialize local variables: obtain the connection block address
|
||
// and crack the FID into its components.
|
||
//
|
||
|
||
connection = WorkContext->Connection;
|
||
|
||
//
|
||
// Acquire the spin lock that guards the connection's file table.
|
||
//
|
||
|
||
ACQUIRE_SPIN_LOCK( &connection->SpinLock, &oldIrql );
|
||
|
||
//
|
||
// See if this is the cached rfcb
|
||
//
|
||
|
||
if ( connection->CachedFid == Fid ) {
|
||
|
||
rfcb = connection->CachedRfcb;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Verify that the FID is in range, is in use, and has the correct
|
||
// sequence number.
|
||
|
||
index = FID_INDEX( Fid );
|
||
sequence = FID_SEQUENCE( Fid );
|
||
tableHeader = &connection->FileTable;
|
||
|
||
if ( (index >= tableHeader->TableSize) ||
|
||
(tableHeader->Table[index].Owner == NULL) ||
|
||
(tableHeader->Table[index].SequenceNumber != sequence) ) {
|
||
|
||
*NtStatus = STATUS_INVALID_HANDLE;
|
||
goto error_exit;
|
||
}
|
||
|
||
rfcb = tableHeader->Table[index].Owner;
|
||
|
||
if ( GET_BLOCK_STATE(rfcb) != BlockStateActive ) {
|
||
|
||
*NtStatus = STATUS_INVALID_HANDLE;
|
||
goto error_exit;
|
||
}
|
||
|
||
//
|
||
// If there is a write behind error, fill in NtStatus and do
|
||
// not return the RFCB pointer.
|
||
//
|
||
|
||
if ( !NT_SUCCESS( rfcb->SavedError ) ) {
|
||
if ( !NT_SUCCESS( rfcb->SavedError ) ) {
|
||
*NtStatus = rfcb->SavedError;
|
||
rfcb->SavedError = STATUS_SUCCESS;
|
||
goto error_exit;
|
||
}
|
||
}
|
||
|
||
connection->CachedRfcb = rfcb;
|
||
connection->CachedFid = (ULONG)Fid;
|
||
|
||
//
|
||
// The FID is valid within the context of this connection. Verify
|
||
// that the owning tree connect's TID is correct.
|
||
//
|
||
// Do not verify the UID for clients that do not understand it.
|
||
//
|
||
|
||
if ( (rfcb->Tid !=
|
||
SmbGetAlignedUshort(&WorkContext->RequestHeader->Tid)) ||
|
||
( (rfcb->Uid !=
|
||
SmbGetAlignedUshort(&WorkContext->RequestHeader->Uid)) &&
|
||
DIALECT_HONORS_UID(connection->SmbDialect) ) ) {
|
||
*NtStatus = STATUS_INVALID_HANDLE;
|
||
goto error_exit;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If a raw write is already active, queue this work item in
|
||
// the RFCB pending completion of the raw write.
|
||
//
|
||
|
||
if ( rfcb->RawWriteCount != 0 ) {
|
||
|
||
InsertTailList(
|
||
&rfcb->RawWriteSerializationList,
|
||
&WorkContext->ListEntry
|
||
);
|
||
|
||
WorkContext->FspRestartRoutine = SrvRestartSmbReceived;
|
||
*NtStatus = STATUS_SUCCESS;
|
||
goto error_exit;
|
||
}
|
||
|
||
//
|
||
// The file is active and the TID is valid. Reference the
|
||
// RFCB and increment the raw write count. Release the spin
|
||
// lock (we don't need it anymore).
|
||
//
|
||
|
||
rfcb->BlockHeader.ReferenceCount++;
|
||
UPDATE_REFERENCE_HISTORY( rfcb, FALSE );
|
||
|
||
rfcb->RawWriteCount++;
|
||
|
||
RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql );
|
||
|
||
//
|
||
// Save the RFCB address in the work context block and
|
||
// return the file address.
|
||
//
|
||
|
||
WorkContext->Rfcb = rfcb;
|
||
ASSERT( GET_BLOCK_TYPE( rfcb->Mfcb ) == BlockTypeMfcb );
|
||
|
||
//
|
||
// Mark the rfcb as active
|
||
//
|
||
|
||
rfcb->IsActive = TRUE;
|
||
|
||
return rfcb;
|
||
|
||
error_exit:
|
||
|
||
//
|
||
// Either the FID is invalid for this connection, the file is
|
||
// closing, or the TID and UID don't match. Clear the file address
|
||
// in the work context block, and return a file address of NULL.
|
||
//
|
||
|
||
WorkContext->Rfcb = NULL; // connection spinlock must be held
|
||
RELEASE_SPIN_LOCK( &connection->SpinLock, oldIrql );
|
||
|
||
return SRV_INVALID_RFCB_POINTER;
|
||
|
||
} // SrvVerifyFidForRawWrite
|
||
|
||
|
||
PSEARCH
|
||
SrvVerifySid (
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN USHORT Index,
|
||
IN USHORT Sequence,
|
||
IN PSRV_DIRECTORY_INFORMATION DirectoryInformation,
|
||
IN CLONG BufferSize
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Verifies the SID in the resume key of a Search or Find SMB. If the
|
||
SID is valid, the address of the search block corresponding to the
|
||
SID is returned. The appropiate fields in the DirectoryInformation
|
||
structure are filled in so that SrvQueryDirectoryFile may be called.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to the work context block for the
|
||
current SMB. In particular, the Connection block pointer is
|
||
used to find the appropriate search table.
|
||
|
||
ResumeKey - a pointer the the resume key to evaluate.
|
||
|
||
Return Value:
|
||
|
||
PSEARCH - address of the Search block, or NULL.
|
||
|
||
--*/
|
||
|
||
{
|
||
PCONNECTION connection;
|
||
PTABLE_HEADER tableHeader;
|
||
PSEARCH search;
|
||
|
||
PAGED_CODE( );
|
||
|
||
connection = WorkContext->Connection;
|
||
|
||
//
|
||
// Acquire the connection's lock.
|
||
//
|
||
|
||
ACQUIRE_LOCK( &connection->Lock );
|
||
|
||
//
|
||
// Verify that the index is in range, that the search block is in use,
|
||
// and that the resume key has the correct sequence number.
|
||
//
|
||
|
||
tableHeader = &connection->PagedConnection->SearchTable;
|
||
if ( (Index < tableHeader->TableSize) &&
|
||
(tableHeader->Table[Index].Owner != NULL) &&
|
||
(tableHeader->Table[Index].SequenceNumber == Sequence) ) {
|
||
|
||
search = tableHeader->Table[Index].Owner;
|
||
|
||
//
|
||
// The SID is valid. Verify that the search block is still
|
||
// active.
|
||
//
|
||
// !!! Does this really apply for search blocks?
|
||
//
|
||
|
||
if ( GET_BLOCK_STATE(search) != BlockStateActive || search->InUse ) {
|
||
|
||
//
|
||
// The search block is no longer active or somebody is
|
||
// already using the search block.
|
||
//
|
||
|
||
search = NULL;
|
||
|
||
} else {
|
||
|
||
//
|
||
// We found a legitimate search block, so reference it.
|
||
//
|
||
|
||
SrvReferenceSearch( search );
|
||
|
||
//
|
||
// Fill in fields of DirectoryInformation.
|
||
//
|
||
|
||
DirectoryInformation->DirectoryHandle = search->DirectoryHandle;
|
||
DirectoryInformation->CurrentEntry = NULL;
|
||
DirectoryInformation->BufferLength = BufferSize -
|
||
sizeof(SRV_DIRECTORY_INFORMATION);
|
||
DirectoryInformation->Wildcards = search->Wildcards;
|
||
DirectoryInformation->ErrorOnFileOpen = FALSE;
|
||
|
||
//
|
||
// Indicate that the search is being used.
|
||
//
|
||
|
||
search->InUse = TRUE;
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// The SID is invalid.
|
||
//
|
||
|
||
search = NULL;
|
||
|
||
}
|
||
|
||
//
|
||
// Release the lock and return the search block address (or NULL).
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
return search;
|
||
|
||
} // SrvVerifySid
|
||
|
||
|
||
PTREE_CONNECT
|
||
SrvVerifyTid (
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN USHORT Tid
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Verifies the TID in an incoming SMB. If the TID is valid, the
|
||
address of the tree connect block corresponding to the TID is
|
||
returned, and the block is referenced.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to the work context block for the
|
||
current SMB. In particular, the Connection block pointer is
|
||
used to find the appropriate tree table. Also, the tree connect
|
||
block address, if the TID is valid, is stored in
|
||
WorkContext->TreeConnect.
|
||
|
||
Tid - Supplies the TID sent in the request SMB
|
||
|
||
Return Value:
|
||
|
||
PTREE_CONNECT - Address of the tree connect block, or NULL
|
||
|
||
--*/
|
||
|
||
{
|
||
PCONNECTION connection;
|
||
PTREE_CONNECT treeConnect;
|
||
PTABLE_HEADER tableHeader;
|
||
USHORT index;
|
||
USHORT sequence;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// If the TID has already been verified, return the tree connect
|
||
// pointer.
|
||
//
|
||
|
||
if ( WorkContext->TreeConnect != NULL ) {
|
||
return WorkContext->TreeConnect;
|
||
}
|
||
|
||
//
|
||
// Initialize local variables: obtain the connection block address
|
||
// and crack the TID into its components.
|
||
//
|
||
|
||
connection = WorkContext->Connection;
|
||
index = TID_INDEX( Tid );
|
||
sequence = TID_SEQUENCE( Tid );
|
||
|
||
//
|
||
// Acquire the connection's tree connect lock.
|
||
//
|
||
|
||
ACQUIRE_LOCK( &connection->Lock );
|
||
|
||
//
|
||
// Verify that the TID is in range, is in use, and has the correct
|
||
// sequence number.
|
||
|
||
tableHeader = &connection->PagedConnection->TreeConnectTable;
|
||
if ( (index < tableHeader->TableSize) &&
|
||
(tableHeader->Table[index].Owner != NULL) &&
|
||
(tableHeader->Table[index].SequenceNumber == sequence) ) {
|
||
|
||
treeConnect = tableHeader->Table[index].Owner;
|
||
|
||
//
|
||
// The TID is valid within the context of this connection.
|
||
// Verify that the tree connect is still active.
|
||
//
|
||
|
||
if ( GET_BLOCK_STATE(treeConnect) == BlockStateActive ) {
|
||
|
||
//
|
||
// The tree connect is active. Reference it.
|
||
//
|
||
|
||
SrvReferenceTreeConnect( treeConnect );
|
||
|
||
} else {
|
||
|
||
//
|
||
// The tree connect is closing.
|
||
//
|
||
|
||
treeConnect = NULL;
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// The TID is invalid for this connection.
|
||
//
|
||
|
||
treeConnect = NULL;
|
||
|
||
}
|
||
|
||
//
|
||
// Release the lock, save the tree connect address in the work context
|
||
// block, and return the tree connect address (or NULL).
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
WorkContext->TreeConnect = treeConnect;
|
||
|
||
return treeConnect;
|
||
|
||
} // SrvVerifyTid
|
||
|
||
|
||
PSESSION
|
||
SrvVerifyUid (
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN USHORT Uid
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Verifies the UID in an incoming SMB. If the UID is valid, the
|
||
address of the session block corresponding to the UID is returned,
|
||
and the block is referenced.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to the work context block for the
|
||
current SMB. In particular, the Connection block pointer is
|
||
used to find the appropriate user table. Also, the session block
|
||
address, if the UID is valid, is stored in WorkContext->Session.
|
||
|
||
Uid - Supplies the UID sent in the request SMB
|
||
|
||
Return Value:
|
||
|
||
PSESSION - Address of the session block, or NULL
|
||
|
||
--*/
|
||
|
||
{
|
||
PCONNECTION connection;
|
||
PTABLE_HEADER tableHeader;
|
||
PSESSION session;
|
||
USHORT index;
|
||
USHORT sequence;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// If the UID has already been verified, return the session pointer.
|
||
//
|
||
|
||
if ( WorkContext->Session != NULL ) {
|
||
return WorkContext->Session;
|
||
}
|
||
|
||
//
|
||
// Initialize local variables: obtain the connection block address
|
||
// and crack the UID into its components.
|
||
//
|
||
|
||
connection = WorkContext->Connection;
|
||
index = UID_INDEX( Uid );
|
||
sequence = UID_SEQUENCE( Uid );
|
||
|
||
//
|
||
// Acquire the connection's session lock.
|
||
//
|
||
|
||
ACQUIRE_LOCK( &connection->Lock );
|
||
|
||
//
|
||
// If this is a down-level (LAN Man 1.0 or earlier) client, than
|
||
// we will not receive a UID, and there will only be one session
|
||
// per connection. Reference that session.
|
||
//
|
||
|
||
tableHeader = &connection->PagedConnection->SessionTable;
|
||
if (!DIALECT_HONORS_UID(connection->SmbDialect) ) {
|
||
|
||
session = tableHeader->Table[0].Owner;
|
||
|
||
} else if ( (index < tableHeader->TableSize) &&
|
||
(tableHeader->Table[index].Owner != NULL) &&
|
||
(tableHeader->Table[index].SequenceNumber == sequence) ) {
|
||
|
||
//
|
||
// The UID is in range, is in use, and has the correct sequence
|
||
// number.
|
||
//
|
||
|
||
session = tableHeader->Table[index].Owner;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The UID is invalid for this connection.
|
||
//
|
||
|
||
IF_DEBUG( ERRORS ) {
|
||
KdPrint(( "SrvVerifyUid: index %d, size %d\n", index, tableHeader->TableSize ));
|
||
if( index < tableHeader->TableSize ) {
|
||
KdPrint((" Owner %p, Table.SequenceNumber %d, seq %d\n",
|
||
tableHeader->Table[index].Owner,
|
||
tableHeader->Table[index].SequenceNumber,
|
||
sequence
|
||
));
|
||
}
|
||
}
|
||
|
||
session = NULL;
|
||
}
|
||
|
||
if ( session != NULL ) {
|
||
|
||
//
|
||
// The UID is valid within the context of this connection.
|
||
// Verify that the session is still active.
|
||
//
|
||
|
||
if ( GET_BLOCK_STATE(session) == BlockStateActive ) {
|
||
|
||
LARGE_INTEGER liNow;
|
||
|
||
KeQuerySystemTime( &liNow);
|
||
|
||
if( session->LogonSequenceInProgress == FALSE &&
|
||
liNow.QuadPart >= session->LogOffTime.QuadPart )
|
||
{
|
||
IF_DEBUG( ERRORS ) {
|
||
KdPrint(( "SrvVerifyUid: LogOffTime has passed %x %x\n",
|
||
session->LogOffTime.HighPart,
|
||
session->LogOffTime.LowPart
|
||
));
|
||
}
|
||
|
||
// Mark the session as expired
|
||
session->IsSessionExpired = TRUE;
|
||
KdPrint(( "Marking session as expired.\n" ));
|
||
}
|
||
|
||
//
|
||
// The session is active. Reference it.
|
||
//
|
||
|
||
SrvReferenceSession( session );
|
||
|
||
//
|
||
// Update the last use time for autologoff.
|
||
//
|
||
|
||
session->LastUseTime = liNow;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The session is closing.
|
||
//
|
||
|
||
IF_DEBUG( ERRORS ) {
|
||
KdPrint(( "SrvVerifyUid: Session state %x\n", GET_BLOCK_STATE( session ) ));
|
||
}
|
||
|
||
session = NULL;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Release the lock, save the session address in the work context
|
||
// block, and return the session address (or NULL).
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
WorkContext->Session = session;
|
||
|
||
return session;
|
||
|
||
} // SrvVerifyUid
|
||
|
||
|
||
NTSTATUS
|
||
SrvVerifyUidAndTid (
|
||
IN PWORK_CONTEXT WorkContext,
|
||
OUT PSESSION *Session,
|
||
OUT PTREE_CONNECT *TreeConnect,
|
||
IN SHARE_TYPE ShareType
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Verifies the UID and TID in an incoming SMB. If both the UID and
|
||
the TDI are valid, the addresses of the session/tree connect blocks
|
||
corresponding to the UID/TID are returned, and the blocks are
|
||
referenced.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies a pointer to the work context block for the
|
||
current SMB. In particular, the Connection block pointer is
|
||
used to find the appropriate user table. If the UID and TID are
|
||
valid, the session/tree connect block addresses are stored in
|
||
WorkContext->Session and WorkContext->TreeConnect.
|
||
|
||
Uid - Supplies the UID sent in the request SMB
|
||
|
||
Tid - Supplies the TID sent in the request SMB
|
||
|
||
Session - Returns a pointer to the session block
|
||
|
||
TreeConnect - Returns a pointer to the tree connect block
|
||
|
||
ShareType - the type of share it should be
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - STATUS_SUCCESS, STATUS_SMB_BAD_UID, or STATUS_SMB_BAD_TID
|
||
|
||
--*/
|
||
|
||
{
|
||
PCONNECTION connection;
|
||
PSESSION session;
|
||
PTREE_CONNECT treeConnect;
|
||
PPAGED_CONNECTION pagedConnection;
|
||
PTABLE_HEADER tableHeader;
|
||
USHORT index;
|
||
USHORT Uid;
|
||
USHORT Tid;
|
||
USHORT sequence;
|
||
LARGE_INTEGER liNow;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// If the UID and TID have already been verified, don't do all this
|
||
// work again.
|
||
//
|
||
|
||
if ( (WorkContext->Session != NULL) &&
|
||
(WorkContext->TreeConnect != NULL) ) {
|
||
|
||
if( ShareType != ShareTypeWild &&
|
||
WorkContext->TreeConnect->Share->ShareType != ShareType ) {
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
*Session = WorkContext->Session;
|
||
*TreeConnect = WorkContext->TreeConnect;
|
||
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Obtain the connection block address and lock the connection.
|
||
//
|
||
|
||
connection = WorkContext->Connection;
|
||
pagedConnection = connection->PagedConnection;
|
||
|
||
ACQUIRE_LOCK( &connection->Lock );
|
||
|
||
//
|
||
// If we haven't negotiated successfully with this client, then we have
|
||
// a failure
|
||
//
|
||
if( connection->SmbDialect == SmbDialectIllegal) {
|
||
RELEASE_LOCK( &connection->Lock );
|
||
return STATUS_INVALID_SMB;
|
||
}
|
||
|
||
//
|
||
// If the UID has already been verified, don't verify it again.
|
||
//
|
||
|
||
if ( WorkContext->Session != NULL ) {
|
||
|
||
session = WorkContext->Session;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Crack the UID into its components.
|
||
//
|
||
|
||
Uid = SmbGetAlignedUshort( &WorkContext->RequestHeader->Uid ),
|
||
index = UID_INDEX( Uid );
|
||
sequence = UID_SEQUENCE( Uid );
|
||
|
||
//
|
||
// If this is a down-level (LAN Man 1.0 or earlier) client, than
|
||
// we will not receive a UID, and there will only be one session
|
||
// per connection. Reference that session.
|
||
//
|
||
// For clients that do send UIDs, verify that the UID is in
|
||
// range, is in use, and has the correct sequence number, and
|
||
// that the session is not closing.
|
||
//
|
||
|
||
tableHeader = &pagedConnection->SessionTable;
|
||
|
||
|
||
if (!DIALECT_HONORS_UID(connection->SmbDialect))
|
||
{
|
||
session = tableHeader->Table[0].Owner;
|
||
}
|
||
else if( (index >= tableHeader->TableSize) ||
|
||
((session = tableHeader->Table[index].Owner) == NULL) ||
|
||
(tableHeader->Table[index].SequenceNumber != sequence) ||
|
||
(GET_BLOCK_STATE(session) != BlockStateActive) )
|
||
{
|
||
|
||
//
|
||
// The UID is invalid for this connection, or the session is
|
||
// closing.
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
return STATUS_SMB_BAD_UID;
|
||
|
||
}
|
||
|
||
//
|
||
// it's valid
|
||
//
|
||
|
||
KeQuerySystemTime(&liNow);
|
||
|
||
if( session == NULL )
|
||
{
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
return STATUS_SMB_BAD_UID;
|
||
}
|
||
|
||
if( session->LogonSequenceInProgress == FALSE &&
|
||
liNow.QuadPart >= session->LogOffTime.QuadPart )
|
||
{
|
||
// Mark the session as expired
|
||
session->IsSessionExpired = TRUE;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// The UID is valid. Check the TID. If the TID has already been
|
||
// verified, don't verify it again.
|
||
//
|
||
|
||
if ( WorkContext->TreeConnect != NULL ) {
|
||
|
||
treeConnect = WorkContext->TreeConnect;
|
||
|
||
} else {
|
||
|
||
//
|
||
// Crack the TID into its components.
|
||
//
|
||
|
||
Tid = SmbGetAlignedUshort( &WorkContext->RequestHeader->Tid ),
|
||
index = TID_INDEX( Tid );
|
||
sequence = TID_SEQUENCE( Tid );
|
||
|
||
//
|
||
// Verify that the TID is in range, is in use, and has the
|
||
// correct sequence number, and that the tree connect is not
|
||
// closing.
|
||
//
|
||
|
||
tableHeader = &pagedConnection->TreeConnectTable;
|
||
if ( (index >= tableHeader->TableSize) ||
|
||
((treeConnect = tableHeader->Table[index].Owner) == NULL) ||
|
||
(tableHeader->Table[index].SequenceNumber != sequence) ||
|
||
(GET_BLOCK_STATE(treeConnect) != BlockStateActive) ) {
|
||
|
||
//
|
||
// The TID is invalid for this connection, or the tree
|
||
// connect is closing.
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
return STATUS_SMB_BAD_TID;
|
||
|
||
}
|
||
|
||
//
|
||
// Make sure this is not a Null session trying to sneak in
|
||
// through an established tree connect.
|
||
//
|
||
|
||
if ( session->IsNullSession &&
|
||
SrvRestrictNullSessionAccess &&
|
||
( treeConnect->Share->ShareType != ShareTypePipe ) ) {
|
||
|
||
|
||
BOOLEAN matchFound = FALSE;
|
||
ULONG i;
|
||
|
||
ACQUIRE_LOCK_SHARED( &SrvConfigurationLock );
|
||
|
||
for ( i = 0; SrvNullSessionShares[i] != NULL ; i++ ) {
|
||
|
||
if ( _wcsicmp(
|
||
SrvNullSessionShares[i],
|
||
treeConnect->Share->ShareName.Buffer
|
||
) == 0 ) {
|
||
|
||
matchFound = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
RELEASE_LOCK( &SrvConfigurationLock );
|
||
|
||
//
|
||
// The null session is not allowed to access this share - reject.
|
||
//
|
||
|
||
if ( !matchFound ) {
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
return(STATUS_ACCESS_DENIED);
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// Both the UID and the TID are valid. Reference the session and
|
||
// tree connect blocks.
|
||
//
|
||
|
||
if ( WorkContext->Session == NULL ) {
|
||
|
||
SrvReferenceSession( session );
|
||
WorkContext->Session = session;
|
||
|
||
//
|
||
// Update the last use time for autologoff.
|
||
//
|
||
|
||
session->LastUseTime = liNow;
|
||
|
||
}
|
||
|
||
if ( WorkContext->TreeConnect == NULL ) {
|
||
|
||
SrvReferenceTreeConnect( treeConnect );
|
||
WorkContext->TreeConnect = treeConnect;
|
||
|
||
}
|
||
|
||
//
|
||
// Release the connection lock and return success.
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
*Session = session;
|
||
*TreeConnect = treeConnect;
|
||
|
||
//
|
||
// Make sure this is the correct type of share
|
||
//
|
||
if( ShareType != ShareTypeWild && (*TreeConnect)->Share->ShareType != ShareType ) {
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvVerifyUidAndTid
|
||
|
||
|
||
BOOLEAN
|
||
SrvReceiveBufferShortage (
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function calculates if the server is running low on receive
|
||
work items that are not involved in blocking operations.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
TRUE - The server is running short on receive work items.
|
||
FALSE - The server is *not* running short on receive work items.
|
||
|
||
--*/
|
||
|
||
{
|
||
KIRQL oldIrql;
|
||
BOOLEAN bufferShortage;
|
||
PWORK_QUEUE queue = PROCESSOR_TO_QUEUE();
|
||
|
||
//
|
||
// Even if we have reached our limit, we will allow this blocking
|
||
// operation if we have enough free work items to allocate. This
|
||
// will allow the resource thread to allocate more work items to
|
||
// service blocking requests.
|
||
//
|
||
|
||
if ( (queue->FreeWorkItems < queue->MaximumWorkItems) ||
|
||
((queue->FreeWorkItems - SrvBlockingOpsInProgress)
|
||
> SrvMinFreeWorkItemsBlockingIo) ) {
|
||
|
||
//
|
||
// The caller will start a blocking operation. Increment the
|
||
// blocking operation count.
|
||
//
|
||
|
||
InterlockedIncrement( &SrvBlockingOpsInProgress );
|
||
bufferShortage = FALSE;
|
||
|
||
} else {
|
||
|
||
//
|
||
// The server is running short on uncommitted receive work items.
|
||
//
|
||
|
||
bufferShortage = TRUE;
|
||
}
|
||
|
||
return bufferShortage;
|
||
|
||
} // SrvReceiveBufferShortage
|
||
|
||
#if SMBDBG
|
||
|
||
//
|
||
// The following functions are defined in smbgtpt.h. When debug mode is
|
||
// disabled (!SMBDBG), these functions are instead defined as macros.
|
||
//
|
||
|
||
USHORT
|
||
SmbGetUshort (
|
||
IN PSMB_USHORT SrcAddress
|
||
)
|
||
|
||
{
|
||
return (USHORT)(
|
||
( ( (PUCHAR)(SrcAddress) )[0] ) |
|
||
( ( (PUCHAR)(SrcAddress) )[1] << 8 )
|
||
);
|
||
}
|
||
|
||
USHORT
|
||
SmbGetAlignedUshort (
|
||
IN PUSHORT SrcAddress
|
||
)
|
||
|
||
{
|
||
return *(SrcAddress);
|
||
}
|
||
|
||
VOID
|
||
SmbPutUshort (
|
||
OUT PSMB_USHORT DestAddress,
|
||
IN USHORT Value
|
||
)
|
||
|
||
{
|
||
( (PUCHAR)(DestAddress) )[0] = BYTE_0(Value);
|
||
( (PUCHAR)(DestAddress) )[1] = BYTE_1(Value);
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
SmbPutAlignedUshort (
|
||
OUT PUSHORT DestAddress,
|
||
IN USHORT Value
|
||
)
|
||
|
||
{
|
||
*(DestAddress) = (Value);
|
||
return;
|
||
}
|
||
|
||
ULONG
|
||
SmbGetUlong (
|
||
IN PSMB_ULONG SrcAddress
|
||
)
|
||
|
||
{
|
||
return (ULONG)(
|
||
( ( (PUCHAR)(SrcAddress) )[0] ) |
|
||
( ( (PUCHAR)(SrcAddress) )[1] << 8 ) |
|
||
( ( (PUCHAR)(SrcAddress) )[2] << 16 ) |
|
||
( ( (PUCHAR)(SrcAddress) )[3] << 24 )
|
||
);
|
||
}
|
||
|
||
ULONG
|
||
SmbGetAlignedUlong (
|
||
IN PULONG SrcAddress
|
||
)
|
||
|
||
{
|
||
return *(SrcAddress);
|
||
}
|
||
|
||
VOID
|
||
SmbPutUlong (
|
||
OUT PSMB_ULONG DestAddress,
|
||
IN ULONG Value
|
||
)
|
||
|
||
{
|
||
( (PUCHAR)(DestAddress) )[0] = BYTE_0(Value);
|
||
( (PUCHAR)(DestAddress) )[1] = BYTE_1(Value);
|
||
( (PUCHAR)(DestAddress) )[2] = BYTE_2(Value);
|
||
( (PUCHAR)(DestAddress) )[3] = BYTE_3(Value);
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
SmbPutAlignedUlong (
|
||
OUT PULONG DestAddress,
|
||
IN ULONG Value
|
||
)
|
||
|
||
{
|
||
*(DestAddress) = Value;
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
SmbPutDate (
|
||
OUT PSMB_DATE DestAddress,
|
||
IN SMB_DATE Value
|
||
)
|
||
|
||
{
|
||
( (PUCHAR)&(DestAddress)->Ushort )[0] = BYTE_0(Value.Ushort);
|
||
( (PUCHAR)&(DestAddress)->Ushort )[1] = BYTE_1(Value.Ushort);
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
SmbMoveDate (
|
||
OUT PSMB_DATE DestAddress,
|
||
IN PSMB_DATE SrcAddress
|
||
)
|
||
|
||
{
|
||
(DestAddress)->Ushort = (USHORT)(
|
||
( ( (PUCHAR)&(SrcAddress)->Ushort )[0] ) |
|
||
( ( (PUCHAR)&(SrcAddress)->Ushort )[1] << 8 ) );
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
SmbZeroDate (
|
||
IN PSMB_DATE Date
|
||
)
|
||
|
||
{
|
||
(Date)->Ushort = 0;
|
||
}
|
||
|
||
BOOLEAN
|
||
SmbIsDateZero (
|
||
IN PSMB_DATE Date
|
||
)
|
||
|
||
{
|
||
return (BOOLEAN)( (Date)->Ushort == 0 );
|
||
}
|
||
|
||
VOID
|
||
SmbPutTime (
|
||
OUT PSMB_TIME DestAddress,
|
||
IN SMB_TIME Value
|
||
)
|
||
|
||
{
|
||
( (PUCHAR)&(DestAddress)->Ushort )[0] = BYTE_0(Value.Ushort);
|
||
( (PUCHAR)&(DestAddress)->Ushort )[1] = BYTE_1(Value.Ushort);
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
SmbMoveTime (
|
||
OUT PSMB_TIME DestAddress,
|
||
IN PSMB_TIME SrcAddress
|
||
)
|
||
|
||
{
|
||
(DestAddress)->Ushort = (USHORT)(
|
||
( ( (PUCHAR)&(SrcAddress)->Ushort )[0] ) |
|
||
( ( (PUCHAR)&(SrcAddress)->Ushort )[1] << 8 ) );
|
||
return;
|
||
}
|
||
|
||
VOID
|
||
SmbZeroTime (
|
||
IN PSMB_TIME Time
|
||
)
|
||
|
||
{
|
||
(Time)->Ushort = 0;
|
||
}
|
||
|
||
BOOLEAN
|
||
SmbIsTimeZero (
|
||
IN PSMB_TIME Time
|
||
)
|
||
|
||
{
|
||
return (BOOLEAN)( (Time)->Ushort == 0 );
|
||
}
|
||
|
||
#endif // SMBDBG
|
||
|
||
|
||
NTSTATUS
|
||
SrvIoCreateFile (
|
||
IN PWORK_CONTEXT WorkContext,
|
||
OUT PHANDLE FileHandle,
|
||
IN ACCESS_MASK DesiredAccess,
|
||
IN POBJECT_ATTRIBUTES ObjectAttributes,
|
||
OUT PIO_STATUS_BLOCK IoStatusBlock,
|
||
IN PLARGE_INTEGER AllocationSize OPTIONAL,
|
||
IN ULONG FileAttributes,
|
||
IN ULONG ShareAccess,
|
||
IN ULONG Disposition,
|
||
IN ULONG CreateOptions,
|
||
IN PVOID EaBuffer OPTIONAL,
|
||
IN ULONG EaLength,
|
||
IN CREATE_FILE_TYPE CreateFileType,
|
||
IN PVOID ExtraCreateParameters OPTIONAL,
|
||
IN ULONG Options,
|
||
IN PSHARE Share OPTIONAL
|
||
)
|
||
{
|
||
PFILE_OBJECT fileObject;
|
||
PDEVICE_OBJECT deviceObject;
|
||
NTSTATUS status;
|
||
NTSTATUS tempStatus;
|
||
BOOLEAN dispositionModified = FALSE;
|
||
ULONG eventToLog = 0;
|
||
ULONG newUsage;
|
||
ULONG requiredSize;
|
||
SHARE_TYPE shareType = ShareTypeWild;
|
||
UNICODE_STRING fileName, *pName;
|
||
|
||
#if SRVDBG_STATS
|
||
LARGE_INTEGER timeStamp, currentTime;
|
||
LARGE_INTEGER timeDifference;
|
||
#endif
|
||
|
||
PAGED_CODE( );
|
||
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(("\nSrvIoCreateFile:\n" ));
|
||
KdPrint((" Obja->ObjectName <%wZ>\n", ObjectAttributes->ObjectName ));
|
||
KdPrint((" Obja->Attributes %X,", ObjectAttributes->Attributes ));
|
||
KdPrint((" RootDirectory %p,", ObjectAttributes->RootDirectory ));
|
||
KdPrint((" SecurityDescriptor %p,", ObjectAttributes->SecurityDescriptor ));
|
||
KdPrint((" SecurityQOS %p\n", ObjectAttributes->SecurityQualityOfService ));
|
||
KdPrint((" DesiredAccess %X, FileAttributes %X, ShareAccess %X\n",
|
||
DesiredAccess, FileAttributes, ShareAccess ));
|
||
KdPrint((" Disposition %X, CreateOptions %X, EaLength %X\n",
|
||
Disposition, CreateOptions, EaLength ));
|
||
KdPrint((" CreateFileType %X, ExtraCreateParameters %p, Options %X\n",
|
||
CreateFileType, ExtraCreateParameters, Options ));
|
||
}
|
||
|
||
//
|
||
// See if this operation is allowed on this share
|
||
//
|
||
if( ARGUMENT_PRESENT( Share ) ) {
|
||
status = SrvIsAllowedOnAdminShare( WorkContext, Share );
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(("Create disallowed on Admin Share: %X\n", status ));
|
||
}
|
||
return status;
|
||
}
|
||
}
|
||
|
||
//
|
||
// We do not allow the remote opening of structured storage files
|
||
//
|
||
if( (CreateOptions & FILE_STRUCTURED_STORAGE) == FILE_STRUCTURED_STORAGE ) {
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(("Create FILE_STRUCTURED_STORAGE unsupported\n" ));
|
||
}
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// We do not allow opening files by ID. It is too easy to escape the share
|
||
//
|
||
if( CreateOptions & FILE_OPEN_BY_FILE_ID ) {
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(("Create FILE_OPEN_BY_FILE_ID unsupported\n" ));
|
||
}
|
||
return STATUS_NOT_SUPPORTED;
|
||
}
|
||
|
||
//
|
||
// Make sure the client isn't trying to create a file having the name
|
||
// of a DOS device
|
||
//
|
||
SrvGetBaseFileName( ObjectAttributes->ObjectName, &fileName );
|
||
for( pName = SrvDosDevices; pName->Length; pName++ ) {
|
||
if( pName->Length == fileName.Length &&
|
||
RtlCompareUnicodeString( pName, &fileName, TRUE ) == 0 ) {
|
||
//
|
||
// Whoa! We don't want clients trying to create files having a
|
||
// DOS device name
|
||
//
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(("Create open %wZ unsupported\n", &fileName ));
|
||
}
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
}
|
||
|
||
//
|
||
// If this is from the NULL session, allow it to open only certain
|
||
// pipes.
|
||
//
|
||
|
||
if ( CreateFileType != CreateFileTypeMailslot ) {
|
||
|
||
shareType = WorkContext->TreeConnect->Share->ShareType;
|
||
|
||
if( shareType == ShareTypePipe ) {
|
||
if ( WorkContext->Session->IsNullSession ) {
|
||
|
||
if( SrvRestrictNullSessionAccess ) {
|
||
BOOLEAN matchFound = FALSE;
|
||
ULONG i;
|
||
|
||
ACQUIRE_LOCK( &SrvConfigurationLock );
|
||
|
||
for ( i = 0; SrvNullSessionPipes[i] != NULL ; i++ ) {
|
||
|
||
if ( _wcsicmp(
|
||
SrvNullSessionPipes[i],
|
||
ObjectAttributes->ObjectName->Buffer
|
||
) == 0 ) {
|
||
|
||
matchFound = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
RELEASE_LOCK( &SrvConfigurationLock );
|
||
|
||
if ( !matchFound ) {
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(( "Create via NULL session denied\n" ));
|
||
}
|
||
return(STATUS_ACCESS_DENIED);
|
||
}
|
||
}
|
||
|
||
} else if( WorkContext->Session->IsLSNotified == FALSE ) {
|
||
//
|
||
// We have a pipe open request, not a NULL session, and
|
||
// we haven't gotten clearance from the license server yet.
|
||
// If this pipe requires clearance, get a license.
|
||
//
|
||
ULONG i;
|
||
BOOLEAN matchFound = FALSE;
|
||
|
||
ACQUIRE_LOCK( &SrvConfigurationLock );
|
||
|
||
for ( i = 0; SrvPipesNeedLicense[i] != NULL ; i++ ) {
|
||
|
||
if ( _wcsicmp(
|
||
SrvPipesNeedLicense[i],
|
||
ObjectAttributes->ObjectName->Buffer
|
||
) == 0 ) {
|
||
matchFound = TRUE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
RELEASE_LOCK( &SrvConfigurationLock );
|
||
|
||
if( matchFound == TRUE ) {
|
||
status = SrvXsLSOperation( WorkContext->Session,
|
||
XACTSRV_MESSAGE_LSREQUEST );
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(( "Create failed due to license server: %X\n", status ));
|
||
}
|
||
return status;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
//
|
||
// !!! a hack to handle a bug in the Object system and path-based
|
||
// operations on print shares. to test a fix, try from OS/2:
|
||
//
|
||
// copy config.sys \\server\printshare
|
||
//
|
||
|
||
if ( Share == NULL &&
|
||
ObjectAttributes->ObjectName->Length == 0 &&
|
||
ObjectAttributes->RootDirectory == NULL ) {
|
||
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(("Create failed: ObjectName Len == 0, an ! Root directory\n" ));
|
||
}
|
||
return STATUS_OBJECT_PATH_SYNTAX_BAD;
|
||
}
|
||
|
||
//
|
||
// Check desired access against share ACL.
|
||
//
|
||
// This gets a little hairy. Basically, we simply want to check the
|
||
// desired access against the ACL. But this doesn't correctly
|
||
// handle the case where the client only has read access to the
|
||
// share, and wants to (perhaps optionally) create (or overwrite) a
|
||
// file or directory, but only asks for read access to the file. We
|
||
// deal with this problem by, in effect, adding write access to the
|
||
// access requested by the client if the client specifies a
|
||
// dispostion mode that will or may create or overwrite the file.
|
||
//
|
||
// If the client specifies a dispostion that WILL create or
|
||
// overwrite (CREATE, SUPERSEDE, OVERWRITE, or OVERWRITE_IF), we
|
||
// turn on the FILE_WRITE_DATA (aka FILE_ADD_FILE) and
|
||
// FILE_APPEND_DATA (aka FILE_ADD_SUBDIRECTORY) accesses. If the
|
||
// access check fails, we return STATUS_ACCESS_DENIED.
|
||
//
|
||
// If the client specifies optional creation, then we have to be
|
||
// even more tricky. We don't know if the file actually exists, so
|
||
// we can't just reject the request out-of-hand, because if the file
|
||
// does exist, and the client really does have read access to the
|
||
// file, it will look weird if we deny the open. So in this case we
|
||
// turn the OPEN_IF request into an OPEN (fail if doesn't exist)
|
||
// request. If the open fails because the file doesn't exist, we
|
||
// return STATUS_ACCESS_DENIED.
|
||
//
|
||
// Note that this method effectively means that the share ACL cannot
|
||
// distinguish between a user who can write to existing files but
|
||
// who cannot create new files. This is because of the overloading
|
||
// of FILE_WRITE_DATA/FILE_ADD_FILE and
|
||
// FILE_APPEND_DATA/FILE_ADD_SUBDIRECTORY.
|
||
//
|
||
//
|
||
// OK. First, check the access exactly as requested.
|
||
//
|
||
|
||
status = SrvCheckShareFileAccess( WorkContext, DesiredAccess );
|
||
if ( !NT_SUCCESS( status )) {
|
||
//
|
||
// Some clients want ACCESS_DENIED to be in the server class
|
||
// instead of the DOS class when it's due to share ACL
|
||
// restrictions. So we need to keep track of why we're
|
||
// returning ACCESS_DENIED.
|
||
//
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(("Create failed, SrvCheckShareFileAccess returns %X\n", status ));
|
||
}
|
||
|
||
WorkContext->ShareAclFailure = TRUE;
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
} else {
|
||
|
||
//
|
||
// Set it to CreateFileTypeNone so no extra checking is done.
|
||
//
|
||
|
||
CreateFileType = CreateFileTypeNone;
|
||
}
|
||
|
||
//
|
||
// That worked. Now, if the Disposition may or will create or
|
||
// overwrite, do more checking.
|
||
//
|
||
|
||
if ( Disposition != FILE_OPEN ) {
|
||
|
||
status = SrvCheckShareFileAccess(
|
||
WorkContext,
|
||
DesiredAccess | FILE_WRITE_DATA | FILE_APPEND_DATA
|
||
);
|
||
|
||
if ( !NT_SUCCESS( status )) {
|
||
|
||
//
|
||
// The client cannot create or overwrite files. Unless
|
||
// they asked for FILE_OPEN_IF, jump out now.
|
||
//
|
||
|
||
if ( Disposition != FILE_OPEN_IF ) {
|
||
//
|
||
// Some clients want ACCESS_DENIED to be in the server class
|
||
// instead of the DOS class when it's due to share ACL
|
||
// restrictions. So we need to keep track of why we're
|
||
// returning ACCESS_DENIED.
|
||
//
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(("Create failed, SrvCheckShareFileAccess returns ACCESS_DENIED\n"));
|
||
}
|
||
WorkContext->ShareAclFailure = TRUE;
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
//
|
||
// Change OPEN_IF to OPEN, and remember that we did it.
|
||
//
|
||
|
||
Disposition = FILE_OPEN;
|
||
dispositionModified = TRUE;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// If this client is reading from the file, turn off FILE_SEQUENTIAL_ONLY in case
|
||
// caching this file would be beneficial to other clients.
|
||
//
|
||
if( shareType == ShareTypeDisk &&
|
||
!(DesiredAccess & (FILE_WRITE_DATA|FILE_APPEND_DATA)) ) {
|
||
|
||
CreateOptions &= ~FILE_SEQUENTIAL_ONLY;
|
||
}
|
||
|
||
if( SrvMaxNonPagedPoolUsage != 0xFFFFFFFF ) {
|
||
//
|
||
// Make sure that this open will not push the server over its
|
||
// nonpaged and paged quotas.
|
||
//
|
||
|
||
newUsage = InterlockedExchangeAdd(
|
||
(PLONG)&SrvStatistics.CurrentNonPagedPoolUsage,
|
||
IO_FILE_OBJECT_NON_PAGED_POOL_CHARGE
|
||
) + IO_FILE_OBJECT_NON_PAGED_POOL_CHARGE;
|
||
|
||
if ( newUsage > SrvMaxNonPagedPoolUsage ) {
|
||
status = STATUS_INSUFF_SERVER_RESOURCES;
|
||
eventToLog = EVENT_SRV_NONPAGED_POOL_LIMIT;
|
||
goto error_exit1;
|
||
}
|
||
|
||
if ( SrvStatistics.CurrentNonPagedPoolUsage > SrvStatistics.PeakNonPagedPoolUsage) {
|
||
SrvStatistics.PeakNonPagedPoolUsage = SrvStatistics.CurrentNonPagedPoolUsage;
|
||
}
|
||
}
|
||
|
||
if( SrvMaxPagedPoolUsage != 0xFFFFFFFF ) {
|
||
|
||
ASSERT( (LONG)SrvStatistics.CurrentPagedPoolUsage >= 0 );
|
||
newUsage = InterlockedExchangeAdd(
|
||
(PLONG)&SrvStatistics.CurrentPagedPoolUsage,
|
||
IO_FILE_OBJECT_PAGED_POOL_CHARGE
|
||
) + IO_FILE_OBJECT_PAGED_POOL_CHARGE;
|
||
ASSERT( (LONG)SrvStatistics.CurrentPagedPoolUsage >= 0 );
|
||
|
||
if ( newUsage > SrvMaxPagedPoolUsage ) {
|
||
status = STATUS_INSUFF_SERVER_RESOURCES;
|
||
eventToLog = EVENT_SRV_PAGED_POOL_LIMIT;
|
||
goto error_exit;
|
||
}
|
||
|
||
if ( SrvStatistics.CurrentPagedPoolUsage > SrvStatistics.PeakPagedPoolUsage) {
|
||
SrvStatistics.PeakPagedPoolUsage = SrvStatistics.CurrentPagedPoolUsage;
|
||
}
|
||
}
|
||
|
||
|
||
//
|
||
// If Share is specified, we may need to fill up the root share
|
||
// handle of the object attribute.
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT( Share ) && (shareType != ShareTypePrint) ) {
|
||
|
||
//
|
||
// Get the Share root handle.
|
||
//
|
||
|
||
status = SrvGetShareRootHandle( Share );
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
|
||
IF_DEBUG(CREATE) {
|
||
KdPrint(( "SrvIoCreateFile: SrvGetShareRootHandle failed: %X\n",
|
||
status ));
|
||
}
|
||
goto error_exit;
|
||
|
||
}
|
||
|
||
//
|
||
// Fill in the root handle.
|
||
//
|
||
|
||
status = SrvSnapGetRootHandle( WorkContext, &ObjectAttributes->RootDirectory );
|
||
if( !NT_SUCCESS( status ) )
|
||
{
|
||
goto error_exit;
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Impersonate the client. This makes us look like the client for
|
||
// the purpose of checking security. Don't do impersonation if
|
||
// this is a spool file since spool files have admin all access,
|
||
// everybody read access by definition.
|
||
//
|
||
|
||
status = STATUS_SUCCESS;
|
||
|
||
if ( shareType != ShareTypePrint ) {
|
||
status = IMPERSONATE( WorkContext );
|
||
}
|
||
|
||
#if SRVDBG_STATS
|
||
//
|
||
// Get the system time for statistics tracking.
|
||
//
|
||
|
||
KeQuerySystemTime( &timeStamp );
|
||
#endif
|
||
|
||
//
|
||
// Perform the actual open.
|
||
//
|
||
// *** Do not lose the status returned by IoCreateFile! Even if
|
||
// it's a success code. The caller needs to know if it's
|
||
// STATUS_OPLOCK_BREAK_IN_PROGRESS.
|
||
//
|
||
|
||
if( NT_SUCCESS( status ) ) {
|
||
|
||
status = IoCreateFile(
|
||
FileHandle,
|
||
DesiredAccess,
|
||
ObjectAttributes,
|
||
IoStatusBlock,
|
||
AllocationSize,
|
||
FileAttributes,
|
||
ShareAccess,
|
||
Disposition,
|
||
CreateOptions,
|
||
EaBuffer,
|
||
EaLength,
|
||
CreateFileType,
|
||
ExtraCreateParameters,
|
||
Options
|
||
);
|
||
|
||
//
|
||
// If the volume was dismounted, and we can refresh the share root handle,
|
||
// we should try the operation again.
|
||
//
|
||
|
||
if( ARGUMENT_PRESENT( Share ) && SrvRetryDueToDismount( Share, status ) ) {
|
||
|
||
status = SrvSnapGetRootHandle( WorkContext, &ObjectAttributes->RootDirectory );
|
||
if( !NT_SUCCESS( status ) )
|
||
{
|
||
goto error_exit;
|
||
}
|
||
|
||
status = IoCreateFile(
|
||
FileHandle,
|
||
DesiredAccess,
|
||
ObjectAttributes,
|
||
IoStatusBlock,
|
||
AllocationSize,
|
||
FileAttributes,
|
||
ShareAccess,
|
||
Disposition,
|
||
CreateOptions,
|
||
EaBuffer,
|
||
EaLength,
|
||
CreateFileType,
|
||
ExtraCreateParameters,
|
||
Options
|
||
);
|
||
}
|
||
|
||
}
|
||
|
||
#if SRVDBG_STATS
|
||
//
|
||
// Grab the time again.
|
||
//
|
||
|
||
KeQuerySystemTime( ¤tTime );
|
||
#endif
|
||
|
||
//
|
||
// Go back to the server's security context.
|
||
//
|
||
|
||
if ( shareType != ShareTypePrint ) {
|
||
//
|
||
// Calling REVERT() even if the impersonate failed is harmless
|
||
//
|
||
REVERT( );
|
||
}
|
||
|
||
#if SRVDBG_STATS
|
||
//
|
||
// Determine how long the IoCreateFile took.
|
||
//
|
||
timeDifference.QuadPart = currentTime.QuadPart - timeStamp.QuadPart;
|
||
|
||
//
|
||
// Update statistics, including server pool quota statistics if the
|
||
// open didn't succeed.
|
||
//
|
||
|
||
ExInterlockedAddLargeInteger(
|
||
&SrvDbgStatistics.TotalIoCreateFileTime,
|
||
timeDifference,
|
||
&GLOBAL_SPIN_LOCK(Statistics)
|
||
);
|
||
#endif
|
||
//
|
||
// Release the share root handle
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT( Share ) ) {
|
||
SrvReleaseShareRootHandle( Share );
|
||
}
|
||
|
||
if ( NT_SUCCESS(status) ) {
|
||
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(( " ** %wZ, handle %p\n",
|
||
ObjectAttributes->ObjectName, *FileHandle ));
|
||
}
|
||
|
||
tempStatus = SrvVerifyDeviceStackSize(
|
||
*FileHandle,
|
||
TRUE,
|
||
&fileObject,
|
||
&deviceObject,
|
||
NULL
|
||
);
|
||
|
||
if ( !NT_SUCCESS( tempStatus )) {
|
||
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_EXPECTED,
|
||
"SrvIoCreateFile: Verify Device Stack Size failed: %X\n",
|
||
tempStatus,
|
||
NULL
|
||
);
|
||
|
||
SRVDBG_RELEASE_HANDLE( *FileHandle, "FIL", 50, 0 );
|
||
SrvNtClose( *FileHandle, FALSE );
|
||
status = tempStatus;
|
||
} else {
|
||
|
||
//
|
||
// Mark the orgin of this file as remote. This should
|
||
// never fail. If it does, it means it is already set.
|
||
// Check for this in a debug build, but ignore errors
|
||
// in the retail build.
|
||
//
|
||
|
||
#if DBG
|
||
tempStatus =
|
||
#endif
|
||
IoSetFileOrigin( fileObject,
|
||
TRUE );
|
||
|
||
ASSERT( tempStatus == STATUS_SUCCESS );
|
||
|
||
//
|
||
// Remove the reference we added in SrvVerifyDeviceStackSize().
|
||
//
|
||
|
||
ObDereferenceObject( fileObject );
|
||
}
|
||
|
||
}
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
goto error_exit;
|
||
}
|
||
|
||
IF_DEBUG(HANDLES) {
|
||
if ( NT_SUCCESS(status) ) {
|
||
PVOID caller, callersCaller;
|
||
RtlGetCallersAddress( &caller, &callersCaller );
|
||
KdPrint(( "opened handle %p for %wZ (%p %p)\n",
|
||
*FileHandle, ObjectAttributes->ObjectName,
|
||
caller, callersCaller ));
|
||
}
|
||
}
|
||
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint((" Status %X\n", status ));
|
||
}
|
||
|
||
return status;
|
||
|
||
error_exit:
|
||
|
||
if( SrvMaxPagedPoolUsage != 0xFFFFFFFF ) {
|
||
ASSERT( (LONG)SrvStatistics.CurrentPagedPoolUsage >= IO_FILE_OBJECT_PAGED_POOL_CHARGE );
|
||
|
||
InterlockedExchangeAdd(
|
||
(PLONG)&SrvStatistics.CurrentPagedPoolUsage,
|
||
-IO_FILE_OBJECT_PAGED_POOL_CHARGE
|
||
);
|
||
|
||
ASSERT( (LONG)SrvStatistics.CurrentPagedPoolUsage >= 0 );
|
||
}
|
||
|
||
error_exit1:
|
||
|
||
if( SrvMaxNonPagedPoolUsage != 0xFFFFFFFF ) {
|
||
ASSERT( (LONG)SrvStatistics.CurrentNonPagedPoolUsage >= IO_FILE_OBJECT_NON_PAGED_POOL_CHARGE );
|
||
|
||
InterlockedExchangeAdd(
|
||
(PLONG)&SrvStatistics.CurrentNonPagedPoolUsage,
|
||
-IO_FILE_OBJECT_NON_PAGED_POOL_CHARGE
|
||
);
|
||
|
||
ASSERT( (LONG)SrvStatistics.CurrentNonPagedPoolUsage >= 0 );
|
||
}
|
||
|
||
if ( status == STATUS_INSUFF_SERVER_RESOURCES ) {
|
||
|
||
requiredSize = ((eventToLog == EVENT_SRV_NONPAGED_POOL_LIMIT) ?
|
||
IO_FILE_OBJECT_NON_PAGED_POOL_CHARGE :
|
||
IO_FILE_OBJECT_PAGED_POOL_CHARGE);
|
||
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_EXPECTED,
|
||
"SrvIoCreateFile: nonpaged pool limit reached, current = %ld, max = %ld\n",
|
||
newUsage - requiredSize,
|
||
SrvMaxNonPagedPoolUsage
|
||
);
|
||
|
||
SrvLogError(
|
||
SrvDeviceObject,
|
||
eventToLog,
|
||
STATUS_INSUFFICIENT_RESOURCES,
|
||
&requiredSize,
|
||
sizeof(ULONG),
|
||
NULL,
|
||
0
|
||
);
|
||
|
||
} else {
|
||
|
||
//
|
||
// Finish up the access checking started above. If the open failed
|
||
// because the file didn't exist, and we turned off the create-if
|
||
// mode because the client doesn't have write access, change the
|
||
// status to STATUS_ACCESS_DENIED.
|
||
//
|
||
|
||
if ( dispositionModified && (status == STATUS_OBJECT_NAME_NOT_FOUND) ) {
|
||
status = STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
}
|
||
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint((" Status %X\n", status ));
|
||
}
|
||
|
||
return status;
|
||
|
||
} // SrvIoCreateFile
|
||
|
||
|
||
NTSTATUS
|
||
SrvNtClose (
|
||
IN HANDLE Handle,
|
||
IN BOOLEAN QuotaCharged
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Closes a handle, records statistics for number of handles closed,
|
||
total time spent closing handles.
|
||
|
||
Arguments:
|
||
|
||
Handle - the handle to close.
|
||
|
||
QuotaCharged - indicates whether the server's internal quota for
|
||
paged and nonpaged pool was charged for this open.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - result of operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
#if SRVDBG_STATS
|
||
LARGE_INTEGER timeStamp, currentTime;
|
||
LARGE_INTEGER timeDifference;
|
||
#endif
|
||
PEPROCESS process;
|
||
|
||
PAGED_CODE( );
|
||
|
||
#if SRVDBG_STATS
|
||
//
|
||
// SnapShot the system time.
|
||
//
|
||
|
||
KeQuerySystemTime( &timeStamp );
|
||
#endif
|
||
|
||
//
|
||
// Make sure we're in the server FSP.
|
||
//
|
||
|
||
process = IoGetCurrentProcess();
|
||
if ( process != SrvServerProcess ) {
|
||
//KdPrint(( "SRV: Closing handle %x in process %x\n", Handle, process ));
|
||
KeAttachProcess( SrvServerProcess );
|
||
}
|
||
|
||
IF_DEBUG( CREATE ) {
|
||
KdPrint(( "SrvNtClose handle %p\n", Handle ));
|
||
}
|
||
|
||
//
|
||
// Close the handle.
|
||
//
|
||
|
||
status = NtClose( Handle );
|
||
|
||
//
|
||
// Return to the original process.
|
||
//
|
||
|
||
if ( process != SrvServerProcess ) {
|
||
KeDetachProcess();
|
||
}
|
||
|
||
IF_DEBUG( ERRORS ) {
|
||
if ( !NT_SUCCESS( status ) ) {
|
||
KdPrint(( "SRV: NtClose failed: %x\n", status ));
|
||
DbgBreakPoint( );
|
||
}
|
||
}
|
||
|
||
ASSERT( NT_SUCCESS( status ) );
|
||
|
||
#if SRVDBG_STATS
|
||
//
|
||
// Get the time again.
|
||
//
|
||
|
||
KeQuerySystemTime( ¤tTime );
|
||
|
||
//
|
||
// Determine how long the close took.
|
||
//
|
||
|
||
timeDifference.QuadPart = currentTime.QuadPart - timeStamp.QuadPart;
|
||
#endif
|
||
|
||
//
|
||
// Update the relevant statistics, including server quota statistics.
|
||
//
|
||
|
||
#if SRVDBG_STATS
|
||
SrvDbgStatistics.TotalNtCloseTime.QuadPart += timeDifference.QuadPart;
|
||
#endif
|
||
|
||
if ( QuotaCharged ) {
|
||
if( SrvMaxPagedPoolUsage != 0xFFFFFFFF ) {
|
||
ASSERT( (LONG)SrvStatistics.CurrentPagedPoolUsage >= 0 );
|
||
InterlockedExchangeAdd(
|
||
(PLONG)&SrvStatistics.CurrentPagedPoolUsage,
|
||
-(LONG)IO_FILE_OBJECT_PAGED_POOL_CHARGE
|
||
);
|
||
ASSERT( (LONG)SrvStatistics.CurrentPagedPoolUsage >= 0 );
|
||
}
|
||
|
||
if( SrvMaxNonPagedPoolUsage != 0xFFFFFFFF ) {
|
||
ASSERT( (LONG)SrvStatistics.CurrentNonPagedPoolUsage >= 0 );
|
||
InterlockedExchangeAdd(
|
||
(PLONG)&SrvStatistics.CurrentNonPagedPoolUsage,
|
||
-(LONG)IO_FILE_OBJECT_NON_PAGED_POOL_CHARGE
|
||
);
|
||
ASSERT( (LONG)SrvStatistics.CurrentNonPagedPoolUsage >= 0 );
|
||
}
|
||
}
|
||
|
||
INCREMENT_DEBUG_STAT( SrvDbgStatistics.TotalHandlesClosed );
|
||
|
||
IF_DEBUG(HANDLES) {
|
||
PVOID caller, callersCaller;
|
||
RtlGetCallersAddress( &caller, &callersCaller );
|
||
if ( NT_SUCCESS(status) ) {
|
||
KdPrint(( "closed handle %p (%p %p)\n",
|
||
Handle, caller, callersCaller ));
|
||
} else {
|
||
KdPrint(( "closed handle %p (%p %p) FAILED: %X\n",
|
||
Handle, caller, callersCaller, status ));
|
||
}
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvNtClose
|
||
|
||
|
||
NTSTATUS
|
||
SrvVerifyDeviceStackSize (
|
||
IN HANDLE FileHandle,
|
||
IN BOOLEAN ReferenceFileObject,
|
||
OUT PFILE_OBJECT *FileObject,
|
||
OUT PDEVICE_OBJECT *DeviceObject,
|
||
OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Ths routine references the file object associated with the
|
||
file handle and checks whether our work item has sufficient
|
||
irp stack size to handle requests to this device or the device
|
||
associated with this file.
|
||
|
||
Arguments:
|
||
|
||
FileHandle - The handle to an open device or file
|
||
ReferenceFileObject - if TRUE, the file object is left referenced.
|
||
FileObject - The file object associated with the filehandle.
|
||
DeviceObject - the device object associated with the filehandle.
|
||
HandleInformation - if not NULL, returns information about the file handle.
|
||
|
||
Return Value:
|
||
|
||
Status of request.
|
||
|
||
--*/
|
||
{
|
||
|
||
NTSTATUS status;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// Get a pointer to the file object, so that we can directly
|
||
// get the related device object that should contain a count
|
||
// of the irp stack size needed by that device.
|
||
//
|
||
|
||
status = ObReferenceObjectByHandle(
|
||
FileHandle,
|
||
0,
|
||
NULL,
|
||
KernelMode,
|
||
(PVOID *)FileObject,
|
||
HandleInformation
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
|
||
SrvLogServiceFailure( SRV_SVC_OB_REF_BY_HANDLE, status );
|
||
|
||
//
|
||
// This internal error bugchecks the system.
|
||
//
|
||
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_IMPOSSIBLE,
|
||
"SrvVerifyDeviceStackSize: unable to reference file handle 0x%lx",
|
||
FileHandle,
|
||
NULL
|
||
);
|
||
|
||
} else {
|
||
|
||
*DeviceObject = IoGetRelatedDeviceObject( *FileObject );
|
||
|
||
if ( (*DeviceObject)->StackSize > SrvReceiveIrpStackSize ) {
|
||
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvVerifyStackSize: WorkItem Irp StackSize too small. Need %d Allocated %d\n",
|
||
(*DeviceObject)->StackSize+1,
|
||
SrvReceiveIrpStackSize
|
||
);
|
||
|
||
SrvLogSimpleEvent( EVENT_SRV_IRP_STACK_SIZE, STATUS_SUCCESS );
|
||
|
||
ObDereferenceObject( *FileObject );
|
||
*FileObject = NULL;
|
||
status = STATUS_INSUFF_SERVER_RESOURCES;
|
||
|
||
} else if ( !ReferenceFileObject ) {
|
||
|
||
ObDereferenceObject( *FileObject );
|
||
*FileObject = NULL;
|
||
|
||
}
|
||
}
|
||
|
||
return status;
|
||
|
||
} // SrvVerifyDeviceStackSize
|
||
|
||
|
||
NTSTATUS
|
||
SrvImpersonate (
|
||
IN PWORK_CONTEXT WorkContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Impersonates the remote client specified in the Session pointer
|
||
of the work context block.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - a work context block containing a valid pointer to
|
||
a session block.
|
||
|
||
Return Value:
|
||
|
||
status code of the attempt
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
|
||
PAGED_CODE( );
|
||
|
||
ASSERT( WorkContext->Session != NULL );
|
||
|
||
if( !IS_VALID_SECURITY_HANDLE (WorkContext->Session->UserHandle) ) {
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
status = ImpersonateSecurityContext(
|
||
&WorkContext->Session->UserHandle
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"IMPERSONATE: NtSetInformationThread failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_THREAD, status );
|
||
}
|
||
|
||
return status;
|
||
|
||
} // SrvImpersonate
|
||
|
||
|
||
VOID
|
||
SrvRevert (
|
||
VOID
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Reverts to the server FSP's default thread context.
|
||
|
||
Arguments:
|
||
|
||
None.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
|
||
PAGED_CODE( );
|
||
|
||
status = PsAssignImpersonationToken(PsGetCurrentThread(),NULL);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"REVERT: NtSetInformationThread failed: %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_THREAD, status );
|
||
}
|
||
|
||
return;
|
||
|
||
} // SrvRevert
|
||
|
||
|
||
#ifdef INCLUDE_SMB_IFMODIFIED
|
||
NTSTATUS
|
||
SrvSetLastWriteTime (
|
||
IN PRFCB Rfcb,
|
||
IN ULONG LastWriteTimeInSeconds,
|
||
IN ACCESS_MASK GrantedAccess,
|
||
IN BOOLEAN ForceChanges
|
||
)
|
||
#else
|
||
NTSTATUS
|
||
SrvSetLastWriteTime (
|
||
IN PRFCB Rfcb,
|
||
IN ULONG LastWriteTimeInSeconds,
|
||
IN ACCESS_MASK GrantedAccess
|
||
)
|
||
#endif
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Sets the last write time on a file if the specified handle has
|
||
sufficient access. This is used by the Close and Create SMBs to
|
||
ensure that file times on server files are consistent with
|
||
times on clients.
|
||
|
||
Arguments:
|
||
|
||
Rfcb - pointer to the rfcb block that is associated with the file
|
||
which we need to set the lastwrite time on.
|
||
|
||
LastWriteTimeInSeconds - the time, in seconds since 1970, to put
|
||
on the file. If it is 0 or -1, the last write time is not
|
||
changed.
|
||
|
||
GrantedAccess - an access mask specifying the access the specified
|
||
handle has. If it has insufficient access, the file time is
|
||
not changed.
|
||
|
||
ForceChanges - flag set to true if we want to send down the
|
||
SetFileInfo anyway even if the client specified a zero lastWriteTime.
|
||
Useful to force the filesystem to update the file control block now
|
||
rather than at the close.
|
||
|
||
Return Value:
|
||
|
||
NTSTATUS - result of operation.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
FILE_BASIC_INFORMATION fileBasicInfo;
|
||
IO_STATUS_BLOCK ioStatusBlock;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// If the client doesn't want to set the time, don't set it.
|
||
//
|
||
|
||
#ifdef INCLUDE_SMB_IFMODIFIED
|
||
if (ForceChanges && LastWriteTimeInSeconds == 0xFFFFFFFF) {
|
||
|
||
LastWriteTimeInSeconds = 0;
|
||
}
|
||
|
||
if ( Rfcb->ShareType != ShareTypeDisk ||
|
||
( ( LastWriteTimeInSeconds == 0 ) && ! ForceChanges ) ||
|
||
( LastWriteTimeInSeconds == 0xFFFFFFFF ) ) {
|
||
#else
|
||
if ( Rfcb->ShareType != ShareTypeDisk ||
|
||
LastWriteTimeInSeconds == 0 ||
|
||
LastWriteTimeInSeconds == 0xFFFFFFFF ) {
|
||
|
||
#endif
|
||
//
|
||
// If the file was written to, we won't cache the file. This is to
|
||
// ensure the file directory entry gets updated by the file system.
|
||
//
|
||
|
||
if ( Rfcb->WrittenTo ) {
|
||
Rfcb->IsCacheable = FALSE;
|
||
}
|
||
return STATUS_SUCCESS;
|
||
}
|
||
|
||
//
|
||
// Make sure that we have the correct access on the specified handle.
|
||
//
|
||
|
||
CHECK_FILE_INFORMATION_ACCESS(
|
||
GrantedAccess,
|
||
IRP_MJ_SET_INFORMATION,
|
||
FileBasicInformation,
|
||
&status
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
return status;
|
||
}
|
||
|
||
//
|
||
// Set to 0 the fields we don't want to change.
|
||
//
|
||
|
||
fileBasicInfo.CreationTime.QuadPart = 0;
|
||
fileBasicInfo.LastAccessTime.QuadPart = 0;
|
||
fileBasicInfo.ChangeTime.QuadPart = 0;
|
||
fileBasicInfo.FileAttributes = 0;
|
||
|
||
//
|
||
// Set up the last write time.
|
||
//
|
||
|
||
#ifdef INCLUDE_SMB_IFMODIFIED
|
||
if (LastWriteTimeInSeconds == 0) {
|
||
|
||
fileBasicInfo.LastWriteTime.QuadPart = 0;
|
||
|
||
} else {
|
||
#endif
|
||
RtlSecondsSince1970ToTime(
|
||
LastWriteTimeInSeconds,
|
||
&fileBasicInfo.LastWriteTime
|
||
);
|
||
|
||
ExLocalTimeToSystemTime(
|
||
&fileBasicInfo.LastWriteTime,
|
||
&fileBasicInfo.LastWriteTime
|
||
);
|
||
#ifdef INCLUDE_SMB_IFMODIFIED
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// Set the time using the passed-in file handle.
|
||
//
|
||
|
||
status = NtSetInformationFile(
|
||
Rfcb->Lfcb->FileHandle,
|
||
&ioStatusBlock,
|
||
&fileBasicInfo,
|
||
sizeof(FILE_BASIC_INFORMATION),
|
||
FileBasicInformation
|
||
);
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvSetLastWriteTime: NtSetInformationFile returned %X",
|
||
status,
|
||
NULL
|
||
);
|
||
|
||
SrvLogServiceFailure( SRV_SVC_NT_SET_INFO_FILE, status );
|
||
return status;
|
||
}
|
||
|
||
return STATUS_SUCCESS;
|
||
|
||
} // SrvSetLastWriteTime
|
||
|
||
NTSTATUS
|
||
SrvCheckShareFileAccess(
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN ACCESS_MASK FileDesiredAccess
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine checks the desired access against the permissions
|
||
set for this client.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - pointer to the work context block that contains information
|
||
about the request.
|
||
FileDesiredAccess - the desired access.
|
||
|
||
Return Value:
|
||
|
||
Status of operation.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS status = STATUS_SUCCESS;
|
||
SECURITY_SUBJECT_CONTEXT subjectContext;
|
||
PSECURITY_DESCRIPTOR securityDescriptor;
|
||
ACCESS_MASK grantedAccess;
|
||
ACCESS_MASK mappedAccess = FileDesiredAccess;
|
||
PPRIVILEGE_SET privileges = NULL;
|
||
|
||
PAGED_CODE( );
|
||
|
||
ACQUIRE_LOCK_SHARED( WorkContext->TreeConnect->Share->SecurityDescriptorLock );
|
||
|
||
securityDescriptor = WorkContext->TreeConnect->Share->FileSecurityDescriptor;
|
||
|
||
if (securityDescriptor != NULL) {
|
||
|
||
status = IMPERSONATE( WorkContext );
|
||
|
||
if( NT_SUCCESS( status ) ) {
|
||
|
||
SeCaptureSubjectContext( &subjectContext );
|
||
|
||
RtlMapGenericMask( &mappedAccess, &SrvFileAccessMapping );
|
||
|
||
//
|
||
// SYNCHRONIZE does not make any sense for a share ACL
|
||
//
|
||
mappedAccess &= ~SYNCHRONIZE;
|
||
|
||
if ( !SeAccessCheck(
|
||
securityDescriptor,
|
||
&subjectContext,
|
||
FALSE, // Locked ?
|
||
mappedAccess,
|
||
0, // PreviousGrantedAccess
|
||
&privileges,
|
||
&SrvFileAccessMapping,
|
||
UserMode,
|
||
&grantedAccess,
|
||
&status
|
||
) ) {
|
||
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint((
|
||
"SrvCheckShareFileAccess: Status %x, Desired access %x, mappedAccess %x\n",
|
||
status,
|
||
FileDesiredAccess,
|
||
mappedAccess
|
||
));
|
||
}
|
||
}
|
||
|
||
|
||
if ( privileges != NULL ) {
|
||
SeFreePrivileges( privileges );
|
||
}
|
||
|
||
SeReleaseSubjectContext( &subjectContext );
|
||
|
||
REVERT( );
|
||
}
|
||
}
|
||
|
||
RELEASE_LOCK( WorkContext->TreeConnect->Share->SecurityDescriptorLock );
|
||
|
||
return status;
|
||
|
||
} // SrvCheckShareFileAccess
|
||
|
||
VOID
|
||
SrvReleaseShareRootHandle (
|
||
IN PSHARE Share
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine releases the root handle for a given share if the
|
||
shared device is removable (floopy, or cdrom).
|
||
|
||
Arguments:
|
||
|
||
Share - The share for which the root directory handle is to be released.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
PAGED_CODE( );
|
||
|
||
if ( Share->Removable ) {
|
||
|
||
ASSERT( Share->CurrentRootHandleReferences > 0 );
|
||
ACQUIRE_LOCK( &SrvShareLock );
|
||
|
||
if ( --Share->CurrentRootHandleReferences == 0 ) {
|
||
|
||
ASSERT( Share->RootDirectoryHandle != NULL );
|
||
SRVDBG_RELEASE_HANDLE( Share->RootDirectoryHandle, "RTD", 51, Share );
|
||
SrvNtClose( Share->RootDirectoryHandle, FALSE );
|
||
Share->RootDirectoryHandle = NULL;
|
||
SrvDereferenceShare( Share );
|
||
|
||
}
|
||
|
||
RELEASE_LOCK( &SrvShareLock );
|
||
|
||
}
|
||
|
||
return;
|
||
|
||
} // SrvReleaseShareRootHandle
|
||
|
||
VOID
|
||
SrvUpdateVcQualityOfService (
|
||
IN PCONNECTION Connection,
|
||
IN PLARGE_INTEGER CurrentTime OPTIONAL
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Updates the connection quality of service information by
|
||
querying the underlying transport.
|
||
|
||
Arguments:
|
||
|
||
Connection - pointer to the connection whose qos we want to update.
|
||
|
||
CurrentTime - an optional pointer to a large interger containing the
|
||
current time.
|
||
|
||
Return Value:
|
||
|
||
None.
|
||
|
||
--*/
|
||
|
||
{
|
||
NTSTATUS status;
|
||
PTDI_CONNECTION_INFO connectionInfo;
|
||
LARGE_INTEGER currentTime;
|
||
LARGE_INTEGER throughput;
|
||
LARGE_INTEGER linkDelay;
|
||
PPAGED_CONNECTION pagedConnection = Connection->PagedConnection;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// This routine is a no-op on connectionless transports.
|
||
//
|
||
|
||
if ( Connection->Endpoint->IsConnectionless ) {
|
||
|
||
Connection->EnableOplocks = FALSE;
|
||
Connection->EnableRawIo = FALSE;
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Update the connection information
|
||
//
|
||
|
||
if ( ARGUMENT_PRESENT( CurrentTime ) ) {
|
||
|
||
currentTime = *CurrentTime;
|
||
|
||
} else {
|
||
|
||
KeQuerySystemTime( ¤tTime );
|
||
|
||
}
|
||
|
||
//
|
||
// Check if connection info is still valid.
|
||
//
|
||
|
||
if ( pagedConnection->LinkInfoValidTime.QuadPart > currentTime.QuadPart ) {
|
||
return;
|
||
}
|
||
|
||
//
|
||
// We need to update the connection information.
|
||
//
|
||
|
||
connectionInfo = ALLOCATE_NONPAGED_POOL(
|
||
sizeof(TDI_CONNECTION_INFO),
|
||
BlockTypeDataBuffer
|
||
);
|
||
|
||
if ( connectionInfo == NULL ) {
|
||
goto exitquery;
|
||
}
|
||
|
||
//
|
||
// Issue a TdiQueryInformation to get the current connection info
|
||
// from the transport provider for this connection. This is a
|
||
// synchronous operation.
|
||
//
|
||
|
||
status = SrvIssueTdiQuery(
|
||
Connection->FileObject,
|
||
&Connection->DeviceObject,
|
||
(PUCHAR)connectionInfo,
|
||
sizeof(TDI_CONNECTION_INFO),
|
||
TDI_QUERY_CONNECTION_INFO
|
||
);
|
||
|
||
//
|
||
// If the request failed, log an event.
|
||
//
|
||
// *** We special-case STATUS_INVALID_CONNECTION because NBF completes
|
||
// our Accept IRP before it's actually ready to accept requests.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
if ( status != STATUS_INVALID_CONNECTION &&
|
||
status != STATUS_CONNECTION_INVALID ) {
|
||
INTERNAL_ERROR(
|
||
ERROR_LEVEL_UNEXPECTED,
|
||
"SrvUpdateVcQualityOfService: SrvIssueTdiQuery failed: %X\n",
|
||
status,
|
||
NULL
|
||
);
|
||
SrvLogServiceFailure( SRV_SVC_NT_IOCTL_FILE, status );
|
||
}
|
||
|
||
DEALLOCATE_NONPAGED_POOL( connectionInfo );
|
||
goto exitquery;
|
||
}
|
||
|
||
//
|
||
// Set the time when this information becomes invalid.
|
||
//
|
||
|
||
currentTime.QuadPart += SrvLinkInfoValidTime.QuadPart;
|
||
|
||
//
|
||
// Get a positive delay. The TP returns a relative time which
|
||
// is negative.
|
||
//
|
||
|
||
linkDelay.QuadPart = -connectionInfo->Delay.QuadPart;
|
||
if ( linkDelay.QuadPart < 0 ) {
|
||
linkDelay.QuadPart = 0;
|
||
}
|
||
|
||
//
|
||
// Get the throughput
|
||
//
|
||
|
||
throughput = connectionInfo->Throughput;
|
||
|
||
//
|
||
// If connection is reliable, check and see if the delay and throughput
|
||
// are within our limits. If not, the vc is unreliable.
|
||
//
|
||
|
||
Connection->EnableOplocks =
|
||
(BOOLEAN) ( !connectionInfo->Unreliable &&
|
||
throughput.QuadPart >= SrvMinLinkThroughput.QuadPart );
|
||
|
||
DEALLOCATE_NONPAGED_POOL( connectionInfo );
|
||
|
||
//
|
||
// We need to check the delay for Raw I/O.
|
||
//
|
||
|
||
Connection->EnableRawIo =
|
||
(BOOLEAN) ( Connection->EnableOplocks &&
|
||
linkDelay.QuadPart <= SrvMaxLinkDelay.QuadPart );
|
||
|
||
//
|
||
// See if oplocks are always disabled for this connection. We do it
|
||
// here so that Connection->EnableRawIo can be computed correctly.
|
||
//
|
||
|
||
if ( Connection->OplocksAlwaysDisabled ) {
|
||
Connection->EnableOplocks = FALSE;
|
||
}
|
||
|
||
//
|
||
// Access "large" connection QOS fields using a lock, to
|
||
// ensure consistent values.
|
||
//
|
||
|
||
ACQUIRE_LOCK( &Connection->Lock );
|
||
pagedConnection->LinkInfoValidTime = currentTime;
|
||
pagedConnection->Delay = linkDelay;
|
||
pagedConnection->Throughput = throughput;
|
||
RELEASE_LOCK( &Connection->Lock );
|
||
|
||
return;
|
||
|
||
exitquery:
|
||
|
||
Connection->EnableOplocks = TRUE;
|
||
Connection->EnableRawIo = TRUE;
|
||
return;
|
||
|
||
} // SrvUpdateVcQualityOfService
|
||
|
||
|
||
BOOLEAN SRVFASTCALL
|
||
SrvValidateSmb (
|
||
IN PWORK_CONTEXT WorkContext
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This function validates an SMB header.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Pointer to a work context block. The RequestHeader
|
||
and RequestParameter fields must be valid.
|
||
|
||
Return Value:
|
||
|
||
TRUE - The SMB is valid
|
||
FALSE - The SMB is invalid
|
||
|
||
--*/
|
||
|
||
{
|
||
PSMB_HEADER smbHeader;
|
||
UCHAR wordCount = 0;
|
||
PSMB_USHORT byteCount = NULL;
|
||
ULONG availableSpaceForSmb = 0;
|
||
|
||
PAGED_CODE( );
|
||
|
||
smbHeader = WorkContext->RequestHeader;
|
||
|
||
//
|
||
// Did we get an entire SMB? We check here for an SMB that at least goes
|
||
// to the WordCount field.
|
||
//
|
||
if( WorkContext->RequestBuffer->DataLength < sizeof( SMB_HEADER ) + sizeof( UCHAR ) ) {
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SMB of %d bytes too short!\n", availableSpaceForSmb ));
|
||
}
|
||
IF_DEBUG( ERRORS ) {
|
||
KdPrint(("Closing connection %p -- msg too small\n", WorkContext->Connection ));
|
||
}
|
||
//
|
||
// This client has really misbehaved. Nuke it!
|
||
//
|
||
WorkContext->Connection->DisconnectReason = DisconnectBadSMBPacket;
|
||
SrvCloseConnection( WorkContext->Connection, FALSE );
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Does it start with 0xFF S M B ?
|
||
//
|
||
if ( SmbGetAlignedUlong( (PULONG)smbHeader->Protocol ) !=
|
||
SMB_HEADER_PROTOCOL ) {
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SMB does not start with SMB_HEADER_PROTOCOL\n" ));
|
||
}
|
||
IF_DEBUG( ERRORS ) {
|
||
KdPrint(("Closing connection %p -- no ffSMB\n", WorkContext->Connection ));
|
||
}
|
||
//
|
||
// This client has really misbehaved. Nuke it!
|
||
//
|
||
WorkContext->Connection->DisconnectReason = DisconnectBadSMBPacket;
|
||
SrvCloseConnection( WorkContext->Connection, FALSE );
|
||
return FALSE;
|
||
}
|
||
|
||
#if 0
|
||
|
||
if ( smbHeader->Reserved != 0 ) {
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SMB Header->Reserved %x != 0!\n", smbHeader->Reserved ));
|
||
}
|
||
SrvLogInvalidSmb( WorkContext );
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// DOS LM2.1 sets SMB_FLAGS_SERVER_TO_REDIR on an oplock break
|
||
// response, so ignore that bit.
|
||
//
|
||
|
||
if ( (smbHeader->Flags &
|
||
~(INCOMING_SMB_FLAGS | SMB_FLAGS_SERVER_TO_REDIR)) != 0 ) {
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SMB Header->Flags (%x) invalid\n", smbHeader->Flags ));
|
||
}
|
||
SrvLogInvalidSmb( WorkContext );
|
||
return FALSE;
|
||
}
|
||
|
||
if ( (SmbGetAlignedUshort( &smbHeader->Flags2 ) &
|
||
~INCOMING_SMB_FLAGS2) != 0 ) {
|
||
KdPrint(( "ValidatesmbHeader: Flags2 = %lx, valid bits = %lx, "
|
||
"invalid bit(s) = %lx\n",
|
||
SmbGetAlignedUshort( &smbHeader->Flags2 ),
|
||
INCOMING_SMB_FLAGS2,
|
||
SmbGetAlignedUshort( &smbHeader->Flags2 ) &
|
||
~INCOMING_SMB_FLAGS2 ));
|
||
|
||
SrvLogInvalidSmb( WorkContext );
|
||
return FALSE;
|
||
}
|
||
|
||
#endif
|
||
|
||
#if 0
|
||
if( (smbHeader->Command != SMB_COM_LOCKING_ANDX) &&
|
||
(smbHeader->Flags & SMB_FLAGS_SERVER_TO_REDIR) ) {
|
||
|
||
//
|
||
// A client has set the bit indicating that this is a server response
|
||
// packet. This could be an attempt by a client to sneak through a
|
||
// firewall -- because the firewall may be configured to allow incoming
|
||
// responses, but no incomming requests (thereby allowing internal clients
|
||
// to access Internet servers, but not allowing external clients to access
|
||
// internal servers). Reject this SMB.
|
||
//
|
||
//
|
||
|
||
SrvLogInvalidSmb( WorkContext );
|
||
return FALSE;
|
||
}
|
||
#endif
|
||
|
||
if( WorkContext->Connection->SmbDialect == SmbDialectIllegal &&
|
||
smbHeader->Command != SMB_COM_NEGOTIATE ) {
|
||
|
||
//
|
||
// Whoa -- the client sent us an SMB, but we haven't negotiated a dialect
|
||
// yet!
|
||
//
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SMB command %x w/o negotiate!\n", smbHeader->Command ));
|
||
}
|
||
IF_DEBUG( ERRORS ) {
|
||
KdPrint(("Closing connection %p -- no Negotiate\n", WorkContext->Connection ));
|
||
}
|
||
|
||
//
|
||
// This client has really misbehaved. Nuke it!
|
||
//
|
||
WorkContext->Connection->DisconnectReason = DisconnectBadSMBPacket;
|
||
SrvCloseConnection( WorkContext->Connection, FALSE );
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
//
|
||
// Get the WordCount and ByteCount values to make sure that there
|
||
// was enough information sent to satisfy the specifications.
|
||
//
|
||
|
||
wordCount = *((PUCHAR)WorkContext->RequestParameters);
|
||
byteCount = (PSMB_USHORT)( (PCHAR)WorkContext->RequestParameters +
|
||
sizeof(UCHAR) + (wordCount * sizeof(USHORT)) );
|
||
availableSpaceForSmb = WorkContext->RequestBuffer->DataLength -
|
||
PTR_DIFF( WorkContext->ResponseParameters,
|
||
WorkContext->RequestBuffer->Buffer );
|
||
|
||
//
|
||
// Verify all of the fixed valued SMB header fields. Variable
|
||
// valued fields (such as Tid) are verified as needed by the
|
||
// individual SMB handlers.
|
||
//
|
||
|
||
//
|
||
// Make sure that the valid word count was sent across. If the
|
||
// value in the table is -1, then the processing routine will
|
||
// verify the word count, and if the word count is -2 then this
|
||
// is an illegal command which will be caught later.
|
||
//
|
||
// We check whether the word count is negative first since
|
||
// critical smbs like read/write (andX/raw) have -1.
|
||
//
|
||
|
||
//
|
||
// Make sure that the ByteCount lies within the boundaries of
|
||
// the received SMB. Without this test, it would be possible,
|
||
// when at the end of a long AndX chain and WordCount is large,
|
||
// for the server to take an access violation when looking at
|
||
// ByteCount. The location for ByteCount must be at least two
|
||
// bytes short of the end of the buffer, as ByteCount is a
|
||
// USHORT (two bytes).
|
||
//
|
||
|
||
//
|
||
// The WordCount parameter is a byte that indicates the number of
|
||
// word parameters, and ByteCount is a word that indicated the
|
||
// number of following bytes. They do not account for their own
|
||
// sizes, so add sizeof(UCHAR) + sizeof(USHORT) to account for them.
|
||
//
|
||
|
||
if ( ((SrvSmbWordCount[WorkContext->NextCommand] < 0)
|
||
||
|
||
((CHAR)wordCount == SrvSmbWordCount[WorkContext->NextCommand]))
|
||
|
||
&&
|
||
|
||
((PCHAR)byteCount <= (PCHAR)WorkContext->RequestBuffer->Buffer +
|
||
WorkContext->RequestBuffer->DataLength -
|
||
sizeof(USHORT))
|
||
|
||
&&
|
||
|
||
((wordCount*sizeof(USHORT) + sizeof(UCHAR) + sizeof(USHORT) +
|
||
SmbGetUshort( byteCount )) <= availableSpaceForSmb) ) {
|
||
|
||
return(TRUE);
|
||
|
||
}
|
||
|
||
//
|
||
// If we have an NT style WriteAndX, we let the client exceed the negotiated
|
||
// buffer size. We do not need to check the WordCount, because we know that
|
||
// the SMB processor itself checks it.
|
||
//
|
||
if( WorkContext->LargeIndication ) {
|
||
|
||
if( WorkContext->NextCommand == SMB_COM_WRITE_ANDX ) {
|
||
return(TRUE);
|
||
} else {
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "LargeIndication but not WRITE_AND_X (%x) received!\n",
|
||
WorkContext->NextCommand ));
|
||
}
|
||
IF_DEBUG( ERRORS ) {
|
||
KdPrint(("Closing connection %p -- msg too large\n", WorkContext->Connection ));
|
||
}
|
||
//
|
||
// This client has really misbehaved. Nuke it!
|
||
//
|
||
WorkContext->Connection->DisconnectReason = DisconnectBadSMBPacket;
|
||
SrvCloseConnection( WorkContext->Connection, FALSE );
|
||
|
||
return( FALSE );
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Make sure that the valid word count was sent across. If the
|
||
// value in the table is -1, then the processing routine will
|
||
// verify the word count, and if the word count is -2 then this
|
||
// is an illegal command which will be caught later.
|
||
//
|
||
// We check whether the word count is negative first since
|
||
// critical smbs like read/write (andX/raw) have -1.
|
||
//
|
||
|
||
if ( (CHAR)wordCount != SrvSmbWordCount[WorkContext->NextCommand] ) {
|
||
|
||
//
|
||
// Living with sin. The DOS redir sends a word count of 9
|
||
// (instead of 8) on a Transaction secondary SMB. Pretend it
|
||
// sent the correct number.
|
||
//
|
||
|
||
if ( WorkContext->RequestHeader->Command ==
|
||
SMB_COM_TRANSACTION_SECONDARY &&
|
||
IS_DOS_DIALECT( WorkContext->Connection->SmbDialect) &&
|
||
wordCount == 9 ) {
|
||
|
||
wordCount = 8;
|
||
*((PUCHAR)WorkContext->RequestParameters) = 8;
|
||
|
||
byteCount = (PSMB_USHORT)( (PCHAR)WorkContext->RequestParameters +
|
||
sizeof(UCHAR) + (8 * sizeof(USHORT)) );
|
||
|
||
#ifdef INCLUDE_SMB_IFMODIFIED
|
||
} else if ( IS_POSTNT5_DIALECT( WorkContext->Connection->SmbDialect ) &&
|
||
( (( WorkContext->RequestHeader->Command ==
|
||
SMB_COM_NT_CREATE_ANDX ) &&
|
||
(wordCount == SMB_REQ_EXTENDED_NT_CREATE_ANDX2_WORK_COUNT)) ||
|
||
(( WorkContext->RequestHeader->Command ==
|
||
SMB_COM_CLOSE ) && (wordCount == 5)) )) {
|
||
|
||
//
|
||
// Per SethuR's suggestion, some SMB commands will have
|
||
// multiple number of arguments rather than define new
|
||
// SMBs.
|
||
//
|
||
if (((PCHAR)byteCount <= (PCHAR)WorkContext->RequestBuffer->Buffer +
|
||
WorkContext->RequestBuffer->DataLength -
|
||
sizeof(USHORT))
|
||
&&
|
||
((wordCount*sizeof(USHORT) + sizeof(UCHAR) + sizeof(USHORT) +
|
||
SmbGetUshort( byteCount )) <= availableSpaceForSmb) ) {
|
||
|
||
return(TRUE);
|
||
}
|
||
#endif
|
||
} else {
|
||
|
||
//
|
||
// Any other request with an incorrect word count is
|
||
// toast. Reject the request.
|
||
//
|
||
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SMB WordCount incorrect. WordCount=%ld, "
|
||
"should be %ld (command = 0x%lx)\n", wordCount,
|
||
SrvSmbWordCount[WorkContext->NextCommand],
|
||
WorkContext->NextCommand ));
|
||
KdPrint(( " SMB received from %z\n",
|
||
(PCSTRING)&WorkContext->Connection->OemClientMachineNameString ));
|
||
|
||
}
|
||
SrvLogInvalidSmb( WorkContext );
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Make sure that the ByteCount lies within the boundaries of
|
||
// the received SMB. Without this test, it would be possible,
|
||
// when at the end of a long AndX chain and WordCount is large,
|
||
// for the server to take an access violation when looking at
|
||
// ByteCount. The location for ByteCount must be at least two
|
||
// bytes short of the end of the buffer, as ByteCount is a
|
||
// USHORT (two bytes).
|
||
//
|
||
|
||
if ( (PCHAR)byteCount > (PCHAR)WorkContext->RequestBuffer->Buffer +
|
||
WorkContext->RequestBuffer->DataLength -
|
||
sizeof(USHORT) ) {
|
||
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "ByteCount address past end of sent SMB. "
|
||
"ByteCount address=0x%p, "
|
||
"End of buffer=0x%lx\n",
|
||
byteCount,
|
||
WorkContext->RequestBuffer->DataLength ));
|
||
KdPrint(( " SMB received from %z\n",
|
||
(PCSTRING)&WorkContext->Connection->OemClientMachineNameString ));
|
||
|
||
}
|
||
|
||
SrvLogInvalidSmb( WorkContext );
|
||
|
||
IF_DEBUG( ERRORS ) {
|
||
KdPrint(("Closing connection %p -- ByteCount too big\n", WorkContext->Connection ));
|
||
}
|
||
|
||
//
|
||
// This client has really misbehaved. Nuke it!
|
||
//
|
||
WorkContext->Connection->DisconnectReason = DisconnectBadSMBPacket;
|
||
SrvCloseConnection( WorkContext->Connection, FALSE );
|
||
|
||
} else {
|
||
|
||
//
|
||
// if this is an IOCTL smb with category 0x53, set byte count to zero.
|
||
// This is due to a DOS Lm2.0 and Lm2.1 bug which does not zero out
|
||
// the bcc causing the preceding check to fail.
|
||
//
|
||
|
||
if ( (WorkContext->RequestHeader->Command == SMB_COM_IOCTL) &&
|
||
(((PREQ_IOCTL) WorkContext->RequestParameters)->Category == 0x53)
|
||
) {
|
||
|
||
SmbPutUshort( byteCount , 0 );
|
||
return(TRUE);
|
||
}
|
||
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SMB WordCount and/or ByteCount incorrect. "
|
||
"WordCount=%ld, ByteCount=%ld, Space=%ld\n",
|
||
wordCount, SmbGetUshort( byteCount ),
|
||
availableSpaceForSmb ));
|
||
KdPrint(( " SMB received from %z\n",
|
||
(PCSTRING)&WorkContext->Connection->OemClientMachineNameString ));
|
||
|
||
}
|
||
|
||
SrvLogInvalidSmb( WorkContext );
|
||
}
|
||
|
||
return FALSE;
|
||
|
||
} // SrvValidateSmb
|
||
|
||
|
||
NTSTATUS
|
||
SrvWildcardRename(
|
||
IN PUNICODE_STRING FileSpec,
|
||
IN PUNICODE_STRING SourceString,
|
||
OUT PUNICODE_STRING TargetString
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine converts a filespec and a source filename into a
|
||
destination file name. This routine is used to support DOS-based
|
||
wildcard renames.
|
||
|
||
Arguments:
|
||
|
||
FileSpec - The wildcard specification describing the destination file.
|
||
SourceString - Pointer to a string that contains the source file name.
|
||
TargetString - Pointer to a string that will contain the destination name.
|
||
|
||
Return Value:
|
||
|
||
Status of operation.
|
||
|
||
--*/
|
||
{
|
||
PWCHAR currentFileSpec;
|
||
WCHAR delimit;
|
||
PWCHAR buffer;
|
||
PWCHAR source;
|
||
ULONG bufferSize;
|
||
ULONG sourceLeft;
|
||
ULONG i;
|
||
|
||
//
|
||
// This will store the number of bytes we have written to the
|
||
// target buffer so far.
|
||
//
|
||
|
||
ULONG resultLength = 0;
|
||
|
||
PAGED_CODE( );
|
||
|
||
//
|
||
// This points to the current character in the filespec.
|
||
//
|
||
|
||
currentFileSpec = FileSpec->Buffer;
|
||
|
||
//
|
||
// Initialize the pointer and the length of the source buffer.
|
||
//
|
||
|
||
source = SourceString->Buffer;
|
||
sourceLeft = SourceString->Length;
|
||
|
||
//
|
||
// Initialize the pointer and the length of the target buffer.
|
||
//
|
||
|
||
buffer = TargetString->Buffer;
|
||
bufferSize = TargetString->MaximumLength;
|
||
|
||
//
|
||
// Go throught each character in the filespec.
|
||
//
|
||
|
||
for ( i = 0; i < (ULONG)FileSpec->Length ; i += sizeof(WCHAR) ) {
|
||
|
||
if (resultLength < bufferSize) {
|
||
|
||
switch ( *currentFileSpec ) {
|
||
case L':':
|
||
case L'\\':
|
||
return STATUS_OBJECT_NAME_INVALID;
|
||
|
||
case L'*':
|
||
|
||
//
|
||
// Store the next character.
|
||
//
|
||
|
||
delimit = *(currentFileSpec+1);
|
||
|
||
//
|
||
// While we have not exceeded the buffer and
|
||
// we have not reached the end of the source string
|
||
// and the current source character is not equal to
|
||
// the delimeter, copy the source character to the
|
||
// target string.
|
||
//
|
||
|
||
while ( ( resultLength < bufferSize ) &&
|
||
( sourceLeft > 0 ) &&
|
||
( *source != delimit ) ) {
|
||
|
||
*(buffer++) = *(source++);
|
||
sourceLeft -= sizeof(WCHAR);
|
||
resultLength += sizeof(WCHAR);
|
||
}
|
||
break;
|
||
|
||
case L'?': //
|
||
case L'>': // should we even consider >, <, and "
|
||
case L'<': // I'll just put this here to be safe
|
||
|
||
//
|
||
// For each ? in the filespec, we copy one character
|
||
// from the source string.
|
||
//
|
||
|
||
if ( ( *source != L'.' ) && ( sourceLeft > 0 )) {
|
||
|
||
if (resultLength < bufferSize) {
|
||
|
||
*(buffer++) = *(source++);
|
||
sourceLeft -= sizeof(WCHAR);
|
||
resultLength += sizeof(WCHAR);
|
||
|
||
} else {
|
||
|
||
return(STATUS_BUFFER_OVERFLOW);
|
||
|
||
}
|
||
|
||
}
|
||
break;
|
||
|
||
case L'.':
|
||
case L'"':
|
||
|
||
//
|
||
// Discard all the characters from the source string up
|
||
// to . or the end of the string.
|
||
//
|
||
|
||
while ( (*source != L'.') && (sourceLeft > 0) ) {
|
||
source++;
|
||
sourceLeft -= sizeof(WCHAR);
|
||
}
|
||
|
||
*(buffer++) = L'.';
|
||
resultLength += sizeof(WCHAR);
|
||
|
||
if ( sourceLeft > 0 ) {
|
||
source++;
|
||
sourceLeft -= sizeof(WCHAR);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
|
||
//
|
||
// Just copy one to one
|
||
//
|
||
|
||
if ( (*source != L'.') && (sourceLeft > 0)) {
|
||
source++;
|
||
sourceLeft -= sizeof(WCHAR);
|
||
}
|
||
|
||
if (resultLength < bufferSize) {
|
||
*(buffer++) = *currentFileSpec;
|
||
resultLength += sizeof(WCHAR);
|
||
|
||
} else {
|
||
|
||
return(STATUS_BUFFER_OVERFLOW);
|
||
|
||
}
|
||
break;
|
||
}
|
||
|
||
currentFileSpec++;
|
||
|
||
} else {
|
||
return(STATUS_BUFFER_OVERFLOW);
|
||
}
|
||
}
|
||
|
||
TargetString->Length = (USHORT)resultLength;
|
||
|
||
return( STATUS_SUCCESS );
|
||
|
||
} // SrvWildcardRename
|
||
|
||
VOID
|
||
DispatchToOrphanage(
|
||
IN PQUEUEABLE_BLOCK_HEADER Block
|
||
)
|
||
{
|
||
KIRQL oldIrql;
|
||
|
||
ASSERT( Block->BlockHeader.ReferenceCount == 1 );
|
||
|
||
ExInterlockedPushEntrySList(
|
||
&SrvBlockOrphanage,
|
||
&Block->SingleListEntry,
|
||
&GLOBAL_SPIN_LOCK(Fsd)
|
||
);
|
||
|
||
ACQUIRE_GLOBAL_SPIN_LOCK( Fsd, &oldIrql );
|
||
|
||
InterlockedIncrement( &SrvResourceOrphanedBlocks );
|
||
|
||
SrvFsdQueueExWorkItem(
|
||
&SrvResourceThreadWorkItem,
|
||
&SrvResourceThreadRunning,
|
||
CriticalWorkQueue
|
||
);
|
||
|
||
RELEASE_GLOBAL_SPIN_LOCK( Fsd, oldIrql );
|
||
|
||
return;
|
||
} // DispatchToOrphanage
|
||
|
||
NTSTATUS
|
||
SrvIsAllowedOnAdminShare(
|
||
IN PWORK_CONTEXT WorkContext,
|
||
IN PSHARE Share
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine returns STATUS_SUCCESS if the client represented by
|
||
the WorkContext should be allowed to access the Share, if the share
|
||
is an Administrative Disk share.
|
||
|
||
Arguments:
|
||
|
||
WorkContext - the unit of work
|
||
Share - pointer to a share, possibly an administrative share
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS if allowed. Error otherwise.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS status = STATUS_SUCCESS;
|
||
|
||
PAGED_CODE();
|
||
|
||
if( Share->SpecialShare && Share->ShareType == ShareTypeDisk ) {
|
||
|
||
SECURITY_SUBJECT_CONTEXT subjectContext;
|
||
ACCESS_MASK desiredAccess, grantedAccess;
|
||
|
||
status = IMPERSONATE( WorkContext );
|
||
|
||
if( NT_SUCCESS( status ) ) {
|
||
SeCaptureSubjectContext( &subjectContext );
|
||
|
||
if( !SeAccessCheck(
|
||
Share->SecurityDescriptor,
|
||
&subjectContext,
|
||
FALSE,
|
||
SRVSVC_SHARE_CONNECT,
|
||
0L,
|
||
NULL,
|
||
&SrvShareConnectMapping,
|
||
UserMode,
|
||
&grantedAccess,
|
||
&status
|
||
) ) {
|
||
|
||
//
|
||
// We have a non-administrative user trying to access a file
|
||
// through an administrative share. Can't allow that!
|
||
//
|
||
// Some clients want ACCESS_DENIED to be in the server class
|
||
// instead of the DOS class when it's due to share ACL
|
||
// restrictions. So we need to keep track of why we're
|
||
// returning ACCESS_DENIED.
|
||
//
|
||
|
||
WorkContext->ShareAclFailure = TRUE;
|
||
}
|
||
|
||
SeReleaseSubjectContext( &subjectContext );
|
||
|
||
REVERT();
|
||
}
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
NTSTATUS
|
||
SrvRetrieveMaximalAccessRightsForUser(
|
||
CtxtHandle *pUserHandle,
|
||
PSECURITY_DESCRIPTOR pSecurityDescriptor,
|
||
PGENERIC_MAPPING pMapping,
|
||
PACCESS_MASK pMaximalAccessRights)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine retrieves the maximal access rights for this client
|
||
|
||
Arguments:
|
||
|
||
pUserHandle - the users security handle
|
||
|
||
pSecurityDescriptor - the security descriptor
|
||
|
||
pMapping - the mapping of access rights
|
||
|
||
pMaximalAccessRights - the computed rights
|
||
|
||
Return Value:
|
||
|
||
Status of operation.
|
||
|
||
Notes:
|
||
|
||
The srv macros IMPERSONATE is defined in terms of a WORK_CONTEXT. Since
|
||
we desire this routine should be used in all situations even when a
|
||
WORK_CONTEXT is not available the code in SrvImpersonate is duplicated
|
||
over here
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS status;
|
||
PPRIVILEGE_SET privileges = NULL;
|
||
SECURITY_SUBJECT_CONTEXT subjectContext;
|
||
|
||
if( !IS_VALID_SECURITY_HANDLE (*pUserHandle) ) {
|
||
return STATUS_ACCESS_DENIED;
|
||
}
|
||
|
||
status = ImpersonateSecurityContext(
|
||
pUserHandle);
|
||
|
||
if( NT_SUCCESS( status ) ) {
|
||
|
||
SeCaptureSubjectContext( &subjectContext );
|
||
|
||
if (!SeAccessCheck(
|
||
pSecurityDescriptor,
|
||
&subjectContext,
|
||
FALSE, // Locked ?
|
||
MAXIMUM_ALLOWED,
|
||
0, // PreviousGrantedAccess
|
||
&privileges,
|
||
pMapping,
|
||
UserMode,
|
||
pMaximalAccessRights,
|
||
&status
|
||
) ) {
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint((
|
||
"SrvCheckShareFileAccess: Status %x, Desired access %x\n",
|
||
status,
|
||
MAXIMUM_ALLOWED
|
||
));
|
||
}
|
||
}
|
||
|
||
if ( privileges != NULL ) {
|
||
SeFreePrivileges( privileges );
|
||
}
|
||
|
||
SeReleaseSubjectContext( &subjectContext );
|
||
|
||
REVERT();
|
||
|
||
if (status == STATUS_ACCESS_DENIED) {
|
||
*pMaximalAccessRights = 0;
|
||
status = STATUS_SUCCESS;
|
||
}
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
NTSTATUS
|
||
SrvRetrieveMaximalAccessRights(
|
||
IN OUT PWORK_CONTEXT WorkContext,
|
||
OUT PACCESS_MASK pMaximalAccessRights,
|
||
OUT PACCESS_MASK pGuestMaximalAccessRights)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine retrieves the maximal access rights for this client as well
|
||
as a guest based upon the ACLs specified for the file
|
||
|
||
Arguments:
|
||
|
||
WorkContext - pointer to the work context block that contains information
|
||
about the request.
|
||
|
||
pMaximalAccessRights - the maximal access rights for this client.
|
||
|
||
pGuestMaximalAccessRights - the maximal access rights for a guest
|
||
|
||
Return Value:
|
||
|
||
Status of operation.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS status;
|
||
|
||
BOOLEAN SecurityBufferAllocated = FALSE;
|
||
|
||
PRFCB rfcb;
|
||
|
||
ULONG lengthNeeded;
|
||
|
||
LONG SecurityDescriptorBufferLength;
|
||
|
||
PSECURITY_DESCRIPTOR SecurityDescriptorBuffer;
|
||
|
||
GENERIC_MAPPING Mapping = {
|
||
FILE_GENERIC_READ,
|
||
FILE_GENERIC_WRITE,
|
||
FILE_GENERIC_EXECUTE,
|
||
FILE_ALL_ACCESS
|
||
};
|
||
|
||
rfcb = WorkContext->Rfcb;
|
||
|
||
SecurityDescriptorBufferLength = (WorkContext->RequestBuffer->DataLength -
|
||
sizeof(SMB_HEADER) -
|
||
- 4);
|
||
|
||
if (SecurityDescriptorBufferLength > 0) {
|
||
SecurityDescriptorBufferLength &= ~3;
|
||
} else {
|
||
SecurityDescriptorBufferLength = 0;
|
||
}
|
||
|
||
SecurityDescriptorBuffer = ((PCHAR)WorkContext->RequestBuffer->Buffer +
|
||
WorkContext->RequestBuffer->BufferLength -
|
||
SecurityDescriptorBufferLength);
|
||
|
||
status = NtQuerySecurityObject(
|
||
rfcb->Lfcb->FileHandle,
|
||
(DACL_SECURITY_INFORMATION |
|
||
SACL_SECURITY_INFORMATION |
|
||
GROUP_SECURITY_INFORMATION |
|
||
OWNER_SECURITY_INFORMATION),
|
||
SecurityDescriptorBuffer,
|
||
SecurityDescriptorBufferLength,
|
||
&lengthNeeded
|
||
);
|
||
|
||
if (status == STATUS_BUFFER_TOO_SMALL) {
|
||
SecurityDescriptorBuffer = ALLOCATE_HEAP(lengthNeeded,PagedPool);
|
||
|
||
if (SecurityDescriptorBuffer != NULL) {
|
||
SecurityBufferAllocated = TRUE;
|
||
|
||
SecurityDescriptorBufferLength = lengthNeeded;
|
||
|
||
status = NtQuerySecurityObject(
|
||
rfcb->Lfcb->FileHandle,
|
||
(DACL_SECURITY_INFORMATION |
|
||
SACL_SECURITY_INFORMATION |
|
||
GROUP_SECURITY_INFORMATION |
|
||
OWNER_SECURITY_INFORMATION),
|
||
SecurityDescriptorBuffer,
|
||
SecurityDescriptorBufferLength,
|
||
&lengthNeeded
|
||
);
|
||
} else {
|
||
status = STATUS_INSUFFICIENT_RESOURCES;
|
||
}
|
||
}
|
||
|
||
if (status == STATUS_SUCCESS) {
|
||
status = SrvRetrieveMaximalAccessRightsForUser(
|
||
&WorkContext->Session->UserHandle,
|
||
SecurityDescriptorBuffer,
|
||
&Mapping,
|
||
pMaximalAccessRights);
|
||
}
|
||
|
||
// Extract the GUEST access rights
|
||
if (status == STATUS_SUCCESS) {
|
||
status = SrvRetrieveMaximalAccessRightsForUser(
|
||
&SrvNullSessionToken,
|
||
SecurityDescriptorBuffer,
|
||
&Mapping,
|
||
pGuestMaximalAccessRights);
|
||
|
||
}
|
||
|
||
if (SecurityBufferAllocated) {
|
||
FREE_HEAP(SecurityDescriptorBuffer);
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
NTSTATUS
|
||
SrvRetrieveMaximalShareAccessRights(
|
||
IN PWORK_CONTEXT WorkContext,
|
||
OUT PACCESS_MASK pMaximalAccessRights,
|
||
OUT PACCESS_MASK pGuestMaximalAccessRights)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine retrieves the maximal access rights for this client as well
|
||
as a guest based upon the ACLs specified for the share
|
||
|
||
Arguments:
|
||
|
||
WorkContext - pointer to the work context block that contains information
|
||
about the request.
|
||
|
||
pMaximalAccessRights - the maximal access rights for this client.
|
||
|
||
pGuestMaximalAccessRights - the maximal access rights for a guest
|
||
|
||
Return Value:
|
||
|
||
Status of operation.
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS status = STATUS_SUCCESS;
|
||
PSECURITY_DESCRIPTOR securityDescriptor;
|
||
ACCESS_MASK grantedAccess;
|
||
ACCESS_MASK mappedAccess = MAXIMUM_ALLOWED;
|
||
|
||
PAGED_CODE( );
|
||
|
||
ACQUIRE_LOCK_SHARED( WorkContext->TreeConnect->Share->SecurityDescriptorLock );
|
||
|
||
securityDescriptor = WorkContext->TreeConnect->Share->FileSecurityDescriptor;
|
||
|
||
if (securityDescriptor != NULL) {
|
||
status = SrvRetrieveMaximalAccessRightsForUser(
|
||
&WorkContext->Session->UserHandle,
|
||
securityDescriptor,
|
||
&SrvFileAccessMapping,
|
||
pMaximalAccessRights);
|
||
|
||
if (NT_SUCCESS(status)) {
|
||
// Get the guest rights
|
||
status = SrvRetrieveMaximalAccessRightsForUser(
|
||
&SrvNullSessionToken,
|
||
securityDescriptor,
|
||
&SrvFileAccessMapping,
|
||
pGuestMaximalAccessRights);
|
||
}
|
||
} else {
|
||
// No Share Level ACL, Grant maximum access to both the current client
|
||
// as well as guest
|
||
|
||
*pMaximalAccessRights = 0x1ff;
|
||
*pGuestMaximalAccessRights = 0x1ff;
|
||
}
|
||
|
||
RELEASE_LOCK( WorkContext->TreeConnect->Share->SecurityDescriptorLock );
|
||
|
||
return status;
|
||
}
|
||
|
||
NTSTATUS
|
||
SrvUpdateMaximalAccessRightsInResponse(
|
||
IN OUT PWORK_CONTEXT WorkContext,
|
||
OUT PSMB_ULONG pMaximalAccessRightsInResponse,
|
||
OUT PSMB_ULONG pGuestMaximalAccessRightsInResponse
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine updates the maximal access rights fields in an extended
|
||
response. This is used to update these fields in various kinds of
|
||
OPEN requests
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies the address of a Work Context Block
|
||
describing the current request. See smbtypes.h for a more
|
||
complete description of the valid fields.
|
||
|
||
pMaximalAccessRightsInResponse - the maximal access rights field in
|
||
the response
|
||
|
||
pGuestMaximalAccessRightsInResponse - the guest maximal access rights field
|
||
in the response
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS if successful, otherwise appropriate error code
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS status;
|
||
|
||
ACCESS_MASK OwnerMaximalAccessRights = 0;
|
||
ACCESS_MASK GuestMaximalAccessRights = 0;
|
||
|
||
status = SrvRetrieveMaximalAccessRights(
|
||
WorkContext,
|
||
&OwnerMaximalAccessRights,
|
||
&GuestMaximalAccessRights);
|
||
|
||
if (status == STATUS_SUCCESS) {
|
||
SmbPutUlong(
|
||
pMaximalAccessRightsInResponse,
|
||
OwnerMaximalAccessRights
|
||
);
|
||
|
||
SmbPutUlong(
|
||
pGuestMaximalAccessRightsInResponse,
|
||
GuestMaximalAccessRights
|
||
);
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
|
||
NTSTATUS
|
||
SrvUpdateMaximalShareAccessRightsInResponse(
|
||
IN OUT PWORK_CONTEXT WorkContext,
|
||
OUT PSMB_ULONG pMaximalAccessRightsInResponse,
|
||
OUT PSMB_ULONG pGuestMaximalAccessRightsInResponse
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine updates the maximal access rights fields in an extended
|
||
response. This is used to update these fields in various kinds of
|
||
TREE_CONNECT requests
|
||
|
||
Arguments:
|
||
|
||
WorkContext - Supplies the address of a Work Context Block
|
||
describing the current request. See smbtypes.h for a more
|
||
complete description of the valid fields.
|
||
|
||
pMaximalAccessRightsInResponse - the maximal access rights field in
|
||
the response
|
||
|
||
pGuestMaximalAccessRightsInResponse - the guest maximal access rights field
|
||
in the response
|
||
|
||
Return Value:
|
||
|
||
STATUS_SUCCESS if successful, otherwise appropriate error code
|
||
|
||
--*/
|
||
{
|
||
NTSTATUS status;
|
||
|
||
ACCESS_MASK OwnerMaximalAccessRights = 0;
|
||
ACCESS_MASK GuestMaximalAccessRights = 0;
|
||
|
||
status = SrvRetrieveMaximalShareAccessRights(
|
||
WorkContext,
|
||
&OwnerMaximalAccessRights,
|
||
&GuestMaximalAccessRights);
|
||
|
||
if (status == STATUS_SUCCESS) {
|
||
SmbPutUlong(
|
||
pMaximalAccessRightsInResponse,
|
||
OwnerMaximalAccessRights
|
||
);
|
||
|
||
SmbPutUlong(
|
||
pGuestMaximalAccessRightsInResponse,
|
||
GuestMaximalAccessRights
|
||
);
|
||
}
|
||
|
||
return status;
|
||
}
|
||
|
||
VOID SRVFASTCALL
|
||
RestartConsumeSmbData(
|
||
IN OUT PWORK_CONTEXT WorkContext
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This is the restart routine for 'SrvConsumeSmbData'. We need to see if we
|
||
drained the current message from the transport. If we have, then we send
|
||
the response SMB to the client. If we have not, we keep going.
|
||
|
||
--*/
|
||
{
|
||
PIRP irp = WorkContext->Irp;
|
||
PIO_STACK_LOCATION irpSp;
|
||
PTDI_REQUEST_KERNEL_RECEIVE parameters;
|
||
|
||
ASSERT( WorkContext->LargeIndication );
|
||
|
||
//
|
||
// Check to see if we are done. If so, send the response to the client
|
||
//
|
||
if( irp->Cancel ||
|
||
NT_SUCCESS( irp->IoStatus.Status ) ||
|
||
irp->IoStatus.Status != STATUS_BUFFER_OVERFLOW ) {
|
||
|
||
RtlZeroMemory( WorkContext->ResponseHeader + 1, sizeof( SMB_PARAMS ) );
|
||
WorkContext->ResponseBuffer->DataLength = sizeof( SMB_HEADER ) + sizeof( SMB_PARAMS );
|
||
WorkContext->ResponseHeader->Flags |= SMB_FLAGS_SERVER_TO_REDIR;
|
||
|
||
//
|
||
// Send the data!
|
||
//
|
||
SRV_START_SEND_2(
|
||
WorkContext,
|
||
SrvFsdRestartSmbAtSendCompletion,
|
||
NULL,
|
||
NULL
|
||
);
|
||
|
||
return;
|
||
}
|
||
|
||
//
|
||
// Not done yet. Consume more data!
|
||
//
|
||
|
||
WorkContext->Connection->ReceivePending = FALSE;
|
||
|
||
irp->Tail.Overlay.OriginalFileObject = NULL;
|
||
irp->Tail.Overlay.Thread = WorkContext->CurrentWorkQueue->IrpThread;
|
||
DEBUG irp->RequestorMode = KernelMode;
|
||
|
||
//
|
||
// Get a pointer to the next stack location. This one is used to
|
||
// hold the parameters for the device I/O control request.
|
||
//
|
||
irpSp = IoGetNextIrpStackLocation( irp );
|
||
|
||
//
|
||
// Set up the completion routine
|
||
//
|
||
IoSetCompletionRoutine(
|
||
irp,
|
||
SrvFsdIoCompletionRoutine,
|
||
WorkContext,
|
||
TRUE,
|
||
TRUE,
|
||
TRUE
|
||
);
|
||
|
||
WorkContext->FsdRestartRoutine = SrvQueueWorkToFspAtDpcLevel;
|
||
WorkContext->FspRestartRoutine = RestartConsumeSmbData;
|
||
|
||
irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
|
||
irpSp->MinorFunction = (UCHAR)TDI_RECEIVE;
|
||
irpSp->FileObject = WorkContext->Connection->FileObject;
|
||
irpSp->DeviceObject = WorkContext->Connection->DeviceObject;
|
||
irpSp->Flags = 0;
|
||
|
||
parameters = (PTDI_REQUEST_KERNEL_RECEIVE)&irpSp->Parameters;
|
||
parameters->ReceiveLength = WorkContext->ResponseBuffer->BufferLength - sizeof( SMB_HEADER );
|
||
parameters->ReceiveFlags = 0;
|
||
|
||
//
|
||
// Set the buffer's partial mdl to point just after the header for this
|
||
// WriteAndX SMB. We need to preserve the header to make it easier to send
|
||
// back the response.
|
||
//
|
||
|
||
IoBuildPartialMdl(
|
||
WorkContext->RequestBuffer->Mdl,
|
||
WorkContext->RequestBuffer->PartialMdl,
|
||
WorkContext->ResponseHeader + 1,
|
||
parameters->ReceiveLength
|
||
);
|
||
|
||
irp->MdlAddress = WorkContext->RequestBuffer->PartialMdl;
|
||
irp->AssociatedIrp.SystemBuffer = NULL;
|
||
irp->Flags = (ULONG)IRP_BUFFERED_IO; // ???
|
||
|
||
(VOID)IoCallDriver( irpSp->DeviceObject, irp );
|
||
|
||
}
|
||
|
||
SMB_PROCESSOR_RETURN_TYPE
|
||
SrvConsumeSmbData(
|
||
IN OUT PWORK_CONTEXT WorkContext
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
This routine handles the case where we have received a LargeIndication
|
||
from a client (i.e. received SMB exceeds the negotiated buffer size). Some
|
||
error has occurred prior to consuming the entire message. The SMB header is
|
||
already formatted for the response, but we need to consume the rest of the
|
||
incoming data and then send the response.
|
||
|
||
--*/
|
||
{
|
||
if( WorkContext->LargeIndication == FALSE ) {
|
||
return SmbStatusSendResponse;
|
||
}
|
||
|
||
IF_DEBUG( ERRORS ) {
|
||
KdPrint(("SRV: SrvConsumeSmbData, BytesAvailable = %u\n",
|
||
WorkContext->BytesAvailable ));
|
||
}
|
||
|
||
WorkContext->Irp->Cancel = FALSE;
|
||
WorkContext->Irp->IoStatus.Status = STATUS_BUFFER_OVERFLOW;
|
||
RestartConsumeSmbData( WorkContext );
|
||
|
||
return SmbStatusInProgress;
|
||
}
|
||
|
||
BOOLEAN
|
||
SrvIsDottedQuadAddress(
|
||
IN PUNICODE_STRING ServerName
|
||
)
|
||
/*++
|
||
|
||
Routine Description:
|
||
Return true if the ServerName appears to be a dotted quad address along the lines
|
||
of xxx.yyy.zzz.qqq
|
||
|
||
False otherwise
|
||
--*/
|
||
{
|
||
PWCHAR p, ep;
|
||
DWORD numberOfDots = 0;
|
||
DWORD numberOfDigits = 0;
|
||
|
||
PAGED_CODE();
|
||
|
||
//
|
||
// If the address is all digits and contains 3 dots, then we'll figure that it's
|
||
// a dotted quad address.
|
||
//
|
||
|
||
ep = &ServerName->Buffer[ ServerName->Length / sizeof( WCHAR ) ];
|
||
|
||
for( p = ServerName->Buffer; p < ep; p++ ) {
|
||
|
||
if( *p == L'.' ) {
|
||
if( ++numberOfDots > 3 || numberOfDigits == 0 ) {
|
||
return FALSE;
|
||
}
|
||
numberOfDigits = 0;
|
||
|
||
} else if( (*p < L'0' || *p > L'9') || ++numberOfDigits > 3 ) {
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
return (numberOfDots == 3) && (numberOfDigits <= 3);
|
||
}
|