You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7677 lines
208 KiB
7677 lines
208 KiB
/*++
|
|
|
|
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 ) ||
|
|
(Share && (Share->SnapShotEpic != -1) && !SrvDisableDownlevelTimewarp)
|
|
)
|
|
)
|
|
{
|
|
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'.' )
|
|
LARGE_INTEGER SnapShotTime;
|
|
|
|
// Try to parse out a snap token
|
|
// Length = LastValidLocation - Source + sizeof(WCHAR) (since LastValidLocation is potentially a valid character)
|
|
if( SrvSnapParseToken( source, ((ULONG)((PCHAR)LastValidLocation - (PCHAR)source + sizeof(WCHAR))), &SnapShotTime ) )
|
|
{
|
|
if( (WorkContext->SnapShotTime.QuadPart != 0) || (SnapShotTime.QuadPart == 0) )
|
|
{
|
|
// Don't allow 2 timestamps in the same path, or 0-time timestamps. These
|
|
// can cause a variety of confusing or Denail-of-Service situations.
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
WorkContext->SnapShotTime = SnapShotTime;
|
|
|
|
while ( (source <= (PWCH)LastValidLocation) &&
|
|
!IS_UNICODE_PATH_SEPARATOR( *source ) ) {
|
|
source++;
|
|
}
|
|
|
|
#if 0 //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 0 //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
|
|
|
|
NTSTATUS
|
|
SrvGetDWord(
|
|
PWSTR BaseKeyName,
|
|
PWSTR ParameterKeyName,
|
|
LPDWORD Value
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queries a dword registry value key
|
|
|
|
Arguments:
|
|
|
|
BaseKeyName - value of the base key
|
|
ParameterKeyValue - Name of the value parameter to query.
|
|
Value - pointer to where to fill in the value if successful
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
--*/
|
|
{
|
|
UNICODE_STRING unicodeKeyName;
|
|
UNICODE_STRING unicodeParamPath;
|
|
OBJECT_ATTRIBUTES objAttributes;
|
|
HANDLE keyHandle;
|
|
|
|
ULONG lengthNeeded;
|
|
NTSTATUS status;
|
|
BYTE bData[sizeof(KEY_VALUE_PARTIAL_INFORMATION)+2*(sizeof(DWORD))];
|
|
|
|
PKEY_VALUE_PARTIAL_INFORMATION infoBuffer = (PKEY_VALUE_PARTIAL_INFORMATION)bData;
|
|
|
|
PAGED_CODE( );
|
|
|
|
*Value = 0;
|
|
|
|
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) ) {
|
|
return status;
|
|
}
|
|
|
|
status = ZwQueryValueKey(
|
|
keyHandle,
|
|
&unicodeKeyName,
|
|
KeyValuePartialInformation,
|
|
infoBuffer,
|
|
sizeof(KEY_VALUE_PARTIAL_INFORMATION)+2*(sizeof(DWORD)),
|
|
&lengthNeeded
|
|
);
|
|
|
|
NtClose( keyHandle );
|
|
|
|
if ( !NT_SUCCESS(status) ) {
|
|
return status;
|
|
}
|
|
|
|
if( infoBuffer->Type != REG_DWORD ||
|
|
infoBuffer->DataLength < sizeof(DWORD) )
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
*Value = *( (LPDWORD)(infoBuffer->Data) );
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // SrvGetDWord
|
|
|
|
|
|
//
|
|
// NOTE: Any changes made here should also be made to the accompanying routines in the MRXSMB
|
|
// RDR such that NativeLanMan and NativeOS match between the two.
|
|
//
|
|
VOID
|
|
SrvGetOsVersionString(
|
|
VOID
|
|
)
|
|
{
|
|
ULONG Storage[256], Storage2[256], Storage3[256];
|
|
UNICODE_STRING UnicodeString;
|
|
HANDLE hRegistryKey;
|
|
NTSTATUS Status, Status2;
|
|
ULONG BytesRead;
|
|
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
PKEY_VALUE_FULL_INFORMATION Value = (PKEY_VALUE_FULL_INFORMATION)Storage;
|
|
PKEY_VALUE_FULL_INFORMATION Value2 = (PKEY_VALUE_FULL_INFORMATION)Storage2;
|
|
PKEY_VALUE_FULL_INFORMATION Value3 = (PKEY_VALUE_FULL_INFORMATION)Storage3;
|
|
KEY_VALUE_PARTIAL_INFORMATION InitialPartialInformationValue;
|
|
ULONG AllocationLength;
|
|
|
|
PAGED_CODE();
|
|
|
|
RtlInitUnicodeString(&UnicodeString, StrRegOsVersionPath);
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&UnicodeString, // name
|
|
OBJ_CASE_INSENSITIVE, // attributes
|
|
NULL, // root
|
|
NULL); // security descriptor
|
|
|
|
Status = ZwOpenKey (&hRegistryKey, KEY_READ, &ObjectAttributes);
|
|
|
|
if( NT_SUCCESS(Status) )
|
|
{
|
|
RtlInitUnicodeString(&UnicodeString, StrRegVersionBuildNumberKeyName);
|
|
Status = ZwQueryValueKey(
|
|
hRegistryKey,
|
|
&UnicodeString,
|
|
KeyValueFullInformation,
|
|
Value,
|
|
sizeof(Storage),
|
|
&BytesRead);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
RtlInitUnicodeString(&UnicodeString, StrRegVersionProductKeyName );
|
|
Status = ZwQueryValueKey(
|
|
hRegistryKey,
|
|
&UnicodeString,
|
|
KeyValueFullInformation,
|
|
Value3,
|
|
sizeof(Storage3),
|
|
&BytesRead);
|
|
|
|
if( NT_SUCCESS(Status) )
|
|
{
|
|
// Change the data pointers
|
|
PWSTR pProduct = (PWSTR)((PCHAR)Value3 + Value3->DataOffset);
|
|
if( (Value3->DataLength > 20) &&
|
|
(_wcsnicmp( pProduct, L"Microsoft ", 10 ) == 0) )
|
|
{
|
|
Value3->DataLength -= 20;
|
|
Value3->DataOffset += 20;
|
|
}
|
|
|
|
// check for existance of Service Pack String
|
|
RtlInitUnicodeString(&UnicodeString, StrRegVersionSPKeyName);
|
|
Status2 = ZwQueryValueKey(
|
|
hRegistryKey,
|
|
&UnicodeString,
|
|
KeyValueFullInformation,
|
|
Value2,
|
|
sizeof(Storage2),
|
|
&BytesRead);
|
|
|
|
SrvNativeOS.MaximumLength = (USHORT)Value->DataLength + (USHORT)Value3->DataLength + sizeof(WCHAR);
|
|
|
|
if(NT_SUCCESS(Status2)) {
|
|
SrvNativeOS.MaximumLength += (USHORT)Value2->DataLength;
|
|
}
|
|
|
|
SrvNativeOS.Length = 0;
|
|
|
|
SrvNativeOS.Buffer = ALLOCATE_HEAP_COLD(SrvNativeOS.MaximumLength, BlockTypeDataBuffer);
|
|
|
|
if (SrvNativeOS.Buffer != NULL) {
|
|
|
|
RtlZeroMemory( SrvNativeOS.Buffer, SrvNativeOS.MaximumLength );
|
|
|
|
// Copy string
|
|
RtlCopyMemory(SrvNativeOS.Buffer,
|
|
(PCHAR)Value3+Value3->DataOffset,
|
|
Value3->DataLength);
|
|
// Replace NULL with SPACE
|
|
RtlCopyMemory((SrvNativeOS.Buffer +
|
|
(Value3->DataLength/sizeof(WCHAR)) - 1),
|
|
L" ",
|
|
sizeof(WCHAR));
|
|
SrvNativeOS.Length += (USHORT)Value3->DataLength;
|
|
|
|
// Copy next string
|
|
RtlCopyMemory((SrvNativeOS.Buffer +
|
|
(Value3->DataLength/sizeof(WCHAR))),
|
|
(PCHAR)Value+Value->DataOffset,
|
|
Value->DataLength);
|
|
SrvNativeOS.Length += (USHORT)Value->DataLength;
|
|
|
|
if(NT_SUCCESS(Status2)) {
|
|
|
|
// replace NULL with SPACE
|
|
RtlCopyMemory(SrvNativeOS.Buffer +
|
|
(Value3->DataLength + Value->DataLength)/sizeof(WCHAR) - 1,
|
|
L" ",
|
|
sizeof(WCHAR));
|
|
|
|
// Copy last string (including NULL)
|
|
RtlCopyMemory(SrvNativeOS.Buffer +
|
|
(Value3->DataLength + Value->DataLength)/sizeof(WCHAR),
|
|
(PCHAR)Value2+Value2->DataOffset,
|
|
Value2->DataLength);
|
|
SrvNativeOS.Length += (USHORT)Value2->DataLength;
|
|
}
|
|
|
|
Status = RtlUnicodeStringToOemString(
|
|
&SrvOemNativeOS,
|
|
&SrvNativeOS,
|
|
TRUE
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
|
|
FREE_HEAP( SrvNativeOS.Buffer );
|
|
SrvNativeOS.Buffer = NULL;
|
|
}
|
|
|
|
} else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
RtlInitUnicodeString(&UnicodeString, StrRegVersionKeyName);
|
|
Status = ZwQueryValueKey(
|
|
hRegistryKey,
|
|
&UnicodeString,
|
|
KeyValueFullInformation,
|
|
Value,
|
|
sizeof(Storage),
|
|
&BytesRead);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
SrvNativeLanMan.MaximumLength =
|
|
SrvNativeLanMan.Length = (USHORT)Value->DataLength +
|
|
(USHORT)Value3->DataLength + sizeof(WCHAR);
|
|
|
|
SrvNativeLanMan.Buffer = ALLOCATE_HEAP_COLD(SrvNativeLanMan.Length, BlockTypeDataBuffer );
|
|
if (SrvNativeLanMan.Buffer != NULL) {
|
|
|
|
RtlZeroMemory( SrvNativeLanMan.Buffer, SrvNativeLanMan.MaximumLength );
|
|
SrvNativeLanMan.Length = 0;
|
|
|
|
RtlCopyMemory(
|
|
SrvNativeLanMan.Buffer,
|
|
(PCHAR)Value3 + Value3->DataOffset,
|
|
Value3->DataLength);
|
|
RtlCopyMemory(
|
|
(SrvNativeLanMan.Buffer +
|
|
(Value3->DataLength/sizeof(WCHAR)) - 1),
|
|
L" ",
|
|
sizeof(WCHAR));
|
|
SrvNativeLanMan.Length += (USHORT)Value3->DataLength;
|
|
|
|
RtlCopyMemory(
|
|
(SrvNativeLanMan.Buffer +
|
|
(Value3->DataLength/sizeof(WCHAR))),
|
|
(PCHAR)Value+Value->DataOffset,
|
|
Value->DataLength);
|
|
SrvNativeLanMan.Length += (USHORT)Value->DataLength;
|
|
|
|
Status = RtlUnicodeStringToOemString(
|
|
&SrvOemNativeLanMan,
|
|
&SrvNativeLanMan,
|
|
TRUE
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
|
|
FREE_HEAP( SrvNativeOS.Buffer );
|
|
SrvNativeOS.Buffer = NULL;
|
|
|
|
RtlFreeOemString( &SrvOemNativeOS );
|
|
SrvOemNativeOS.Buffer = NULL;
|
|
|
|
FREE_HEAP( SrvNativeLanMan.Buffer );
|
|
SrvNativeLanMan.Buffer = NULL;
|
|
}
|
|
|
|
|
|
} else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
}
|
|
|
|
ZwClose(hRegistryKey);
|
|
}
|
|
|
|
if( !NT_SUCCESS(Status) )
|
|
{
|
|
RtlInitUnicodeString( &SrvNativeOS, StrDefaultNativeOs );
|
|
RtlInitAnsiString( (PANSI_STRING)&SrvOemNativeOS, StrDefaultNativeOsOem );
|
|
RtlInitUnicodeString( &SrvNativeLanMan, StrNativeLanman );
|
|
RtlInitAnsiString( (PANSI_STRING)&SrvOemNativeLanMan, StrNativeLanmanOem );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
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 Security Context, if any.
|
|
//
|
|
|
|
if ( WorkContext->SecurityContext != NULL ) {
|
|
SrvDereferenceSecurityContext( WorkContext->SecurityContext );
|
|
WorkContext->SecurityContext = 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;
|
|
DirectoryInformation->DownlevelTimewarp = search->DownlevelTimewarp;
|
|
|
|
//
|
|
// 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;
|
|
PSECURITY_CONTEXT securityContext;
|
|
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 );
|
|
|
|
securityContext = session->SecurityContext;
|
|
if( securityContext != NULL ) SrvReferenceSecurityContext( securityContext );
|
|
|
|
//
|
|
// 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;
|
|
securityContext = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
securityContext = 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;
|
|
WorkContext->SecurityContext = securityContext;
|
|
|
|
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( );
|
|
|
|
KeQuerySystemTime(&liNow);
|
|
|
|
//
|
|
// 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
|
|
//
|
|
|
|
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;
|
|
|
|
if( session->SecurityContext != NULL )
|
|
{
|
|
SrvReferenceSecurityContext( session->SecurityContext );
|
|
WorkContext->SecurityContext = session->SecurityContext;
|
|
}
|
|
|
|
//
|
|
// 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, &ShareAccess );
|
|
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,
|
|
&ShareAccess
|
|
);
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// Make sure no data is being written
|
|
//
|
|
if( (WorkContext->SnapShotTime.QuadPart != 0) &&
|
|
(DesiredAccess & (FILE_WRITE_DATA|FILE_APPEND_DATA)) )
|
|
{
|
|
status = STATUS_ACCESS_DENIED;
|
|
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 ));
|
|
#if DBG
|
|
DbgBreakPoint( );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
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( WorkContext->SecurityContext == NULL ||
|
|
!IS_VALID_SECURITY_HANDLE (WorkContext->SecurityContext->UserHandle) ) {
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
status = ImpersonateSecurityContext(
|
|
&WorkContext->SecurityContext->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
|
|
|
|
|
|
NTSTATUS
|
|
SrvImpersonateSession (
|
|
IN PSESSION Session
|
|
)
|
|
|
|
/*++
|
|
|
|
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( Session != NULL );
|
|
|
|
ACQUIRE_LOCK( &Session->Connection->Lock );
|
|
|
|
if( Session->SecurityContext == NULL ||
|
|
!IS_VALID_SECURITY_HANDLE (Session->SecurityContext->UserHandle) ) {
|
|
status = STATUS_ACCESS_DENIED;
|
|
}
|
|
else
|
|
{
|
|
status = ImpersonateSecurityContext(
|
|
&Session->SecurityContext->UserHandle
|
|
);
|
|
}
|
|
|
|
RELEASE_LOCK( &Session->Connection->Lock );
|
|
|
|
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
|
|
|
|
|
|
NTSTATUS
|
|
SrvSetLastWriteTime (
|
|
IN PRFCB Rfcb,
|
|
IN ULONG LastWriteTimeInSeconds,
|
|
IN ACCESS_MASK GrantedAccess
|
|
)
|
|
/*++
|
|
|
|
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.
|
|
//
|
|
|
|
if ( Rfcb->ShareType != ShareTypeDisk ||
|
|
LastWriteTimeInSeconds == 0 ||
|
|
LastWriteTimeInSeconds == 0xFFFFFFFF ) {
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
RtlSecondsSince1970ToTime(
|
|
LastWriteTimeInSeconds,
|
|
&fileBasicInfo.LastWriteTime
|
|
);
|
|
|
|
ExLocalTimeToSystemTime(
|
|
&fileBasicInfo.LastWriteTime,
|
|
&fileBasicInfo.LastWriteTime
|
|
);
|
|
|
|
//
|
|
// 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,
|
|
IN OUT PULONG ShareMode
|
|
)
|
|
|
|
/*++
|
|
|
|
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.
|
|
ShareMode - The sharing mode desired. This can be manipulated to prevent Read-Deny Read on
|
|
shares marked as such, to prevent a variety of DoS attacks
|
|
|
|
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( );
|
|
|
|
//
|
|
// For Restrict Opens behavior, we no longer allow someone to lock the file exclusively if they are opening
|
|
// simple for read. This prevents cases where clients with read-only access can lock a file and prevent anyone
|
|
// else from reading it. It presents interesting DoS situations in policy.
|
|
//
|
|
if( WorkContext->TreeConnect->Share->ShareProperties & SHI1005_FLAGS_RESTRICT_EXCLUSIVE_OPENS )
|
|
{
|
|
if( (!((*ShareMode) & FILE_SHARE_READ) &&
|
|
!(FileDesiredAccess & (FILE_WRITE_DATA|FILE_WRITE_ATTRIBUTES|FILE_WRITE_EA|FILE_APPEND_DATA)) ) )
|
|
{
|
|
*ShareMode |= FILE_SHARE_READ;
|
|
}
|
|
}
|
|
|
|
if( WorkContext->TreeConnect->Share->ShareProperties & SHI1005_FLAGS_FORCE_SHARED_DELETE )
|
|
{
|
|
*ShareMode |= FILE_SHARE_DELETE;
|
|
}
|
|
|
|
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 and ACCESS_SYSTEM_SECURITY do not make any sense for a share ACL
|
|
//
|
|
mappedAccess &= ~(SYNCHRONIZE|ACCESS_SYSTEM_SECURITY);
|
|
|
|
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)) );
|
|
|
|
} 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->SecurityContext->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->SecurityContext->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);
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
SrvIsLocalHost(
|
|
IN PUNICODE_STRING ServerName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
Return true if the ServerName represents the loopback connection
|
|
|
|
False otherwise
|
|
--*/
|
|
{
|
|
UNICODE_STRING LocalHost = { 18, 18, L"localhost" };
|
|
|
|
return !RtlCompareUnicodeString( ServerName, &LocalHost, TRUE );
|
|
}
|
|
|
|
PSESSION
|
|
SrvFindSession(
|
|
IN PCONNECTION connection,
|
|
IN USHORT Uid
|
|
)
|
|
{
|
|
PTABLE_HEADER tableHeader;
|
|
PSESSION session;
|
|
USHORT index;
|
|
USHORT sequence;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Initialize local variables: obtain the connection block address
|
|
// and crack the UID into its components.
|
|
//
|
|
|
|
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.
|
|
//
|
|
|
|
session = NULL;
|
|
}
|
|
|
|
if( session && GET_BLOCK_STATE(session) == BlockStateActive )
|
|
{
|
|
SrvReferenceSession( session );
|
|
}
|
|
else
|
|
{
|
|
session = NULL;
|
|
}
|
|
|
|
RELEASE_LOCK( &connection->Lock );
|
|
|
|
return session;
|
|
}
|
|
|
|
NTSTATUS
|
|
DuplicateSystemHandle(PKPROCESS InitialSystemProcess,
|
|
HANDLE FileHandle,
|
|
HANDLE *NewFileHandle)
|
|
{
|
|
HANDLE SystemProcess;
|
|
NTSTATUS Status;
|
|
|
|
if (NewFileHandle == NULL) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
Status = ObOpenObjectByPointer(
|
|
InitialSystemProcess,
|
|
0,
|
|
NULL,
|
|
0,
|
|
NULL, //PsProcessType,
|
|
KernelMode,
|
|
&SystemProcess
|
|
);
|
|
|
|
if ( !NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
Status = ZwDuplicateObject(
|
|
SystemProcess,
|
|
FileHandle,
|
|
NtCurrentProcess(),
|
|
NewFileHandle,
|
|
0,
|
|
0,
|
|
DUPLICATE_SAME_ATTRIBUTES | DUPLICATE_SAME_ACCESS
|
|
);
|
|
|
|
// close system process
|
|
ZwClose (SystemProcess);
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvCreateHandleWithOptions(PUNICODE_STRING FileName,
|
|
HANDLE FileHandle,
|
|
ULONG Options,
|
|
ACCESS_MASK Access,
|
|
ULONG Share,
|
|
BOOLEAN KernelHandle,
|
|
HANDLE *Handle)
|
|
{
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
NTSTATUS err;
|
|
UNICODE_STRING cwspath;
|
|
WCHAR *name;
|
|
WCHAR *prefix = L"\\??\\";
|
|
ULONG length;
|
|
|
|
length = (wcslen(prefix) * sizeof(WCHAR)) + FileName->MaximumLength + sizeof(WCHAR);
|
|
name = ALLOCATE_HEAP( length, BlockTypeMisc );
|
|
if (name == NULL) {
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
cwspath.Buffer = name;
|
|
cwspath.MaximumLength = (USHORT)length;
|
|
cwspath.Length = 0;
|
|
RtlAppendUnicodeToString(&cwspath, prefix);
|
|
RtlAppendUnicodeStringToString(&cwspath, FileName);
|
|
|
|
InitializeObjectAttributes(&ObjectAttributes,
|
|
&cwspath,
|
|
OBJ_CASE_INSENSITIVE |
|
|
((KernelHandle == TRUE) ? OBJ_KERNEL_HANDLE : 0),
|
|
NULL,
|
|
0);
|
|
|
|
err = IoCreateFileSpecifyDeviceObjectHint (Handle,
|
|
Access,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
NULL,
|
|
0L,
|
|
Share,
|
|
FILE_OPEN,
|
|
Options,
|
|
NULL,
|
|
0,
|
|
CreateFileTypeNone,
|
|
NULL,
|
|
IO_IGNORE_SHARE_ACCESS_CHECK |
|
|
IO_NO_PARAMETER_CHECKING,
|
|
NULL);
|
|
|
|
FREE_HEAP(name);
|
|
|
|
return err;
|
|
}
|
|
|
|
NTSTATUS
|
|
SrvProcessHandleDuplicateRequest(
|
|
IN PIRP Irp,
|
|
IN PIO_STACK_LOCATION IrpSp,
|
|
IN PSRV_REQUEST_HANDLE_DUP pDupRequest,
|
|
IN OUT PSRV_RESPONSE_HANDLE_DUP pOutHandleDup
|
|
)
|
|
{
|
|
PRFCB rfcb;
|
|
PSESSION session;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE( );
|
|
|
|
//
|
|
// Try to find a file that matches the file ID. Only an exact
|
|
// match will work.
|
|
//
|
|
|
|
rfcb = SrvFindEntryInOrderedList(
|
|
&SrvRfcbList,
|
|
NULL,
|
|
NULL,
|
|
(ULONG)(pDupRequest->Key.ResumeKey),
|
|
TRUE,
|
|
NULL
|
|
);
|
|
|
|
if ( (rfcb == NULL) ||
|
|
( (UINT64)(rfcb->PagedRfcb->OpenTime.QuadPart) != pDupRequest->Key.Timestamp) ) {
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// Don't do LWIO on TIMEWARP files
|
|
//
|
|
if ( rfcb->Mfcb->SnapShotTime.QuadPart != 0 )
|
|
{
|
|
SrvDereferenceRfcb( rfcb );
|
|
return STATUS_NOT_SUPPORTED;
|
|
}
|
|
|
|
//
|
|
// Validate that the SID matches that used to open the file
|
|
//
|
|
session = SrvFindSession( rfcb->Connection, rfcb->Uid );
|
|
if( !session )
|
|
{
|
|
status = STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
else
|
|
{
|
|
pOutHandleDup->LockKey = rfcb->ShiftedFid | SmbGetUshort(&pDupRequest->Key.Pid);
|
|
|
|
status = SrvImpersonateSession( session );
|
|
|
|
if( NT_SUCCESS(status) )
|
|
{
|
|
if (IoGetCurrentProcess() != SrvServerProcess) {
|
|
status = DuplicateSystemHandle(SrvServerProcess,
|
|
rfcb->Lfcb->FileHandle,
|
|
&pOutHandleDup->hFile);
|
|
|
|
if (pDupRequest->Options != 0 && status == STATUS_SUCCESS) {
|
|
HANDLE tmp;
|
|
|
|
if (SrvCreateHandleWithOptions(&rfcb->Lfcb->Mfcb->FileName,
|
|
pOutHandleDup->hFile,
|
|
pDupRequest->Options,
|
|
rfcb->GrantedAccess,
|
|
rfcb->ShareAccess,
|
|
FALSE,
|
|
&tmp) == STATUS_SUCCESS) {
|
|
ZwClose(pOutHandleDup->hFile);
|
|
pOutHandleDup->hFile = tmp;
|
|
}
|
|
}
|
|
} else {
|
|
status = SrvCreateHandleWithOptions(
|
|
&rfcb->Lfcb->Mfcb->FileName,
|
|
rfcb->Lfcb->FileHandle,
|
|
pDupRequest->Options,
|
|
rfcb->GrantedAccess,
|
|
rfcb->ShareAccess,
|
|
TRUE,
|
|
&pOutHandleDup->hFile);
|
|
}
|
|
|
|
SrvRevert();
|
|
}
|
|
|
|
SrvDereferenceSession( session );
|
|
}
|
|
|
|
SrvDereferenceRfcb( rfcb );
|
|
|
|
return status;
|
|
}
|