1958 lines
56 KiB
C
1958 lines
56 KiB
C
/*++
|
||
|
||
Copyright (c) 1989 Microsoft Corporation
|
||
|
||
Module Name:
|
||
|
||
smbtree.c
|
||
|
||
Abstract:
|
||
|
||
This module contains routines for dealing with tree connects and
|
||
disconnects:
|
||
|
||
Tree Connect
|
||
Tree Connect And X
|
||
Tree Disconnect
|
||
|
||
Author:
|
||
|
||
David Treadwell (davidtr) 15-Nov-1989
|
||
|
||
Revision History:
|
||
|
||
|
||
--*/
|
||
|
||
#include "precomp.h"
|
||
#include "smbtree.tmh"
|
||
#pragma hdrstop
|
||
|
||
#ifdef ALLOC_PRAGMA
|
||
#pragma alloc_text( PAGE, SrvSmbTreeConnect )
|
||
#pragma alloc_text( PAGE, SrvSmbTreeConnectAndX )
|
||
#pragma alloc_text( PAGE, SrvSmbTreeDisconnect )
|
||
#endif
|
||
|
||
|
||
SMB_PROCESSOR_RETURN_TYPE
|
||
SrvSmbTreeConnect (
|
||
SMB_PROCESSOR_PARAMETERS
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Processes a tree connect SMB.
|
||
|
||
Arguments:
|
||
|
||
SMB_PROCESSOR_PARAMETERS - See smbprocs.h for a description
|
||
of the parameters to SMB processor routines.
|
||
|
||
Return Value:
|
||
|
||
SMB_PROCESSOR_RETURN_TYPE - See smbprocs.h
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
PREQ_TREE_CONNECT request;
|
||
PRESP_TREE_CONNECT response;
|
||
|
||
PSESSION session;
|
||
PCONNECTION connection;
|
||
PPAGED_CONNECTION pagedConnection;
|
||
PTABLE_HEADER tableHeader;
|
||
PTABLE_ENTRY entry;
|
||
SHORT tidIndex;
|
||
PSHARE share;
|
||
PTREE_CONNECT treeConnect;
|
||
PSZ password, service;
|
||
USHORT len;
|
||
NTSTATUS status = STATUS_SUCCESS;
|
||
NTSTATUS TableStatus;
|
||
SMB_STATUS SmbStatus = SmbStatusInProgress;
|
||
BOOLEAN didLogon = FALSE;
|
||
SHORT uidIndex;
|
||
SMB_DIALECT smbDialect;
|
||
PUNICODE_STRING clientMachineNameString;
|
||
ACCESS_MASK desiredAccess;
|
||
ACCESS_MASK grantedAccess;
|
||
SECURITY_SUBJECT_CONTEXT subjectContext;
|
||
UNICODE_STRING domain = { 0, 0, StrNull };
|
||
|
||
PAGED_CODE( );
|
||
if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT)
|
||
WorkContext->PreviousSMB = EVENT_TYPE_SMB_TREE_CONNECT;
|
||
SrvWmiStartContext(WorkContext);
|
||
|
||
IF_SMB_DEBUG(TREE1) {
|
||
KdPrint(( "Tree connect request header at 0x%p, response header at 0x%p\n",
|
||
WorkContext->RequestHeader, WorkContext->ResponseHeader ));
|
||
KdPrint(( "Tree connect request parameters at 0x%p, response parameters at 0x%p\n",
|
||
WorkContext->RequestParameters,
|
||
WorkContext->ResponseParameters ));
|
||
}
|
||
|
||
//
|
||
// Set up parameters.
|
||
//
|
||
|
||
request = (PREQ_TREE_CONNECT)(WorkContext->RequestParameters);
|
||
response = (PRESP_TREE_CONNECT)(WorkContext->ResponseParameters);
|
||
|
||
connection = WorkContext->Connection;
|
||
pagedConnection = connection->PagedConnection;
|
||
smbDialect = connection->SmbDialect;
|
||
|
||
//
|
||
// If this client has not yet done a session setup and this his first
|
||
// tree connection then we must first do a logon. (i.e. SessionSetup)
|
||
//
|
||
|
||
len = SrvGetStringLength(
|
||
(PSZ)request->Buffer,
|
||
END_OF_REQUEST_SMB( WorkContext ),
|
||
FALSE, // not unicode
|
||
FALSE // do not include null terminator
|
||
);
|
||
if( len == (USHORT)-1 ) {
|
||
SrvSetSmbError( WorkContext, STATUS_INVALID_SMB );
|
||
status = STATUS_INVALID_SMB;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
password = (PSZ)request->Buffer + 2 + len;
|
||
|
||
len = SrvGetStringLength(
|
||
password,
|
||
END_OF_REQUEST_SMB( WorkContext ),
|
||
FALSE, // not unicode
|
||
FALSE // do not include null terminator
|
||
);
|
||
|
||
if( len == (USHORT)-1 ) {
|
||
SrvSetSmbError( WorkContext, STATUS_INVALID_SMB );
|
||
status = STATUS_INVALID_SMB;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
service = password + (len + 1) + 1;
|
||
|
||
//
|
||
// Allocate a tree connect block. We do this early on the
|
||
// assumption that the request will usually succeed. This also
|
||
// reduces the amount of time that we hold the lock.
|
||
//
|
||
|
||
SrvAllocateTreeConnect( &treeConnect, NULL );
|
||
|
||
if ( treeConnect == NULL ) {
|
||
|
||
//
|
||
// Unable to allocate tree connect. Return an error to the
|
||
// client.
|
||
//
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_INSUFF_SERVER_RESOURCES );
|
||
status = STATUS_INSUFF_SERVER_RESOURCES;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
ASSERT( SrvSessionList.Lock == &SrvOrderedListLock );
|
||
|
||
ACQUIRE_LOCK( &connection->Lock );
|
||
|
||
if ( pagedConnection->CurrentNumberOfSessions != 0 ) {
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
session = SrvVerifyUid (
|
||
WorkContext,
|
||
SmbGetAlignedUshort( &WorkContext->RequestHeader->Uid )
|
||
);
|
||
|
||
if ( session == NULL ) {
|
||
|
||
//
|
||
// This should only happen if the client has already
|
||
// established a session, as in tree connecting with a bad
|
||
// UID.
|
||
//
|
||
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_SMB_BAD_UID );
|
||
status = STATUS_SMB_BAD_UID;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
else if( session->IsSessionExpired )
|
||
{
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
status = SESSION_EXPIRED_STATUS_CODE;
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
} else if ( (smbDialect <= SmbDialectLanMan10) ||
|
||
(smbDialect == SmbDialectIllegal) ) {
|
||
|
||
//
|
||
// An LM 1.0 or newer client has tried to do a tree connect
|
||
// without first doing session setup. We call this a protocol
|
||
// violation.
|
||
//
|
||
// Also catch clients that are trying to connect without
|
||
// negotiating a valid protocol.
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
|
||
if ( smbDialect == SmbDialectIllegal ) {
|
||
|
||
KdPrint(("SrvSmbTreeConnect: Client %z is using an illegal "
|
||
"dialect.\n", (PCSTRING)&connection->OemClientMachineNameString ));;
|
||
|
||
} else {
|
||
|
||
KdPrint(( "Client speaking dialect %ld sent tree connect without session setup.\n", connection->SmbDialect ));
|
||
}
|
||
}
|
||
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_INVALID_SMB );
|
||
status = STATUS_INVALID_SMB;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
} else {
|
||
|
||
UNICODE_STRING machineName;
|
||
PENDPOINT endpoint;
|
||
BOOLEAN seqNumbers;
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
|
||
//
|
||
// Convert the client name to unicode
|
||
//
|
||
|
||
clientMachineNameString = &pagedConnection->ClientMachineNameString;
|
||
if ( clientMachineNameString->Length == 0 ) {
|
||
|
||
UNICODE_STRING clientMachineName;
|
||
clientMachineName.Buffer = pagedConnection->ClientMachineName;
|
||
clientMachineName.MaximumLength =
|
||
(USHORT)(COMPUTER_NAME_LENGTH+1)*sizeof(WCHAR);
|
||
|
||
(VOID)RtlOemStringToUnicodeString(
|
||
&clientMachineName,
|
||
&connection->OemClientMachineNameString,
|
||
FALSE
|
||
);
|
||
|
||
//
|
||
// Add the double backslashes to the length
|
||
//
|
||
|
||
clientMachineNameString->Length =
|
||
(USHORT)(clientMachineName.Length + 2*sizeof(WCHAR));
|
||
|
||
}
|
||
|
||
//
|
||
// Form a string describing the computer name without the
|
||
// leading backslashes.
|
||
//
|
||
|
||
machineName.Buffer = clientMachineNameString->Buffer + 2;
|
||
machineName.Length = clientMachineNameString->Length - 2 * sizeof(WCHAR);
|
||
machineName.MaximumLength =
|
||
clientMachineNameString->MaximumLength - 2 * sizeof(WCHAR);
|
||
|
||
//
|
||
// Allocate a session block.
|
||
//
|
||
|
||
SrvAllocateSession(
|
||
&session,
|
||
&machineName,
|
||
&domain );
|
||
|
||
if ( session == NULL ) {
|
||
|
||
//
|
||
// Unable to allocate a Session block. Return an error
|
||
// status.
|
||
//
|
||
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_INSUFF_SERVER_RESOURCES );
|
||
status = STATUS_INSUFF_SERVER_RESOURCES;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Assume that down-level clients that are getting logged on
|
||
// here will always use canonicalized (uppercase) paths. This
|
||
// will result in case insensitivity for all operations.
|
||
//
|
||
|
||
session->UsingUppercasePaths = TRUE;
|
||
|
||
//
|
||
// The only way for a client to tell us the buffer size or the
|
||
// max count of pending requests he wants to use is the Session
|
||
// Setup SMB. If he didn't send one, then we get to
|
||
// unilaterally determine the buffer size and multiplex count
|
||
// used by both of us.
|
||
//
|
||
|
||
endpoint = connection->Endpoint;
|
||
if ( endpoint->IsConnectionless ) {
|
||
|
||
ULONG adapterNumber;
|
||
|
||
//
|
||
// Our session max buffer size is the smaller of the
|
||
// server receive buffer size and the ipx transport
|
||
// indicated max packet size.
|
||
//
|
||
|
||
adapterNumber =
|
||
WorkContext->ClientAddress->DatagramOptions.LocalTarget.NicId;
|
||
|
||
session->MaxBufferSize =
|
||
(USHORT) GetIpxMaxBufferSize(
|
||
endpoint,
|
||
adapterNumber,
|
||
SrvReceiveBufferLength
|
||
);
|
||
|
||
} else {
|
||
|
||
session->MaxBufferSize = (USHORT)SrvReceiveBufferLength;
|
||
}
|
||
|
||
session->MaxMpxCount = SrvMaxMpxCount;
|
||
|
||
if ( session->MaxMpxCount < 2 ) {
|
||
connection->OplocksAlwaysDisabled = TRUE;
|
||
}
|
||
|
||
|
||
if( SrvSmbSecuritySignaturesRequired == TRUE &&
|
||
WorkContext->Connection->Endpoint->IsConnectionless == FALSE ) {
|
||
|
||
seqNumbers = TRUE;
|
||
|
||
} else {
|
||
seqNumbers = FALSE;
|
||
|
||
}
|
||
|
||
//
|
||
// Try to find legitimate name/password combination.
|
||
//
|
||
|
||
status = SrvValidateUser(
|
||
&session->UserHandle,
|
||
session,
|
||
connection,
|
||
&machineName,
|
||
password,
|
||
strlen( password ) + 1,
|
||
NULL, // CaseSensitivePassword
|
||
0, // CaseSensitivePasswordLength
|
||
seqNumbers,
|
||
NULL // action
|
||
);
|
||
|
||
//
|
||
// If a bad name/password combination was sent, return an error.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
|
||
SrvFreeSession( session );
|
||
SrvFreeTreeConnect ( treeConnect );
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: Bad user/password combination.\n" ));
|
||
}
|
||
|
||
SrvStatistics.LogonErrors++;
|
||
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
IF_SMB_DEBUG(ADMIN1) {
|
||
KdPrint(( "Validated user: %ws\n",
|
||
connection->PagedConnection->ClientMachineName ));
|
||
}
|
||
|
||
//
|
||
// Making a new session visible is a multiple-step operation. It
|
||
// must be inserted in the global ordered tree connect list and the
|
||
// containing connection's session table, and the connection must be
|
||
// referenced. We need to make these operations appear atomic, so
|
||
// that the session cannot be accessed elsewhere before we're done
|
||
// setting it up. In order to do this, we hold all necessary locks
|
||
// the entire time we're doing the operations. The first operation
|
||
// is protected by the global ordered list lock
|
||
// (SrvOrderedListLock), while the other operations are protected by
|
||
// the per-connection lock. We take out the ordered list lock
|
||
// first, then the connection lock. This ordering is required by
|
||
// lock levels (see lock.h).
|
||
//
|
||
//
|
||
// Ready to try to find a UID for the session. Check to see if
|
||
// the connection is being closed, and if so, terminate this
|
||
// operation.
|
||
//
|
||
|
||
ASSERT( SrvSessionList.Lock == &SrvOrderedListLock );
|
||
|
||
ACQUIRE_LOCK( SrvSessionList.Lock );
|
||
ACQUIRE_LOCK( &connection->Lock );
|
||
|
||
if ( GET_BLOCK_STATE(connection) != BlockStateActive ) {
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
RELEASE_LOCK( SrvSessionList.Lock );
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: Connection closing\n" ));
|
||
}
|
||
|
||
SrvFreeSession( session );
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_INVALID_PARAMETER );
|
||
status = STATUS_INVALID_PARAMETER;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Because the client is speaking the "core" dialect, it will
|
||
// not send a valid UID in future SMBs, so it can only have one
|
||
// session. We define that session to live in UID slot 0. We
|
||
// know that the client has no sessions yet, so slot 0 must be
|
||
// free.
|
||
//
|
||
|
||
tableHeader = &pagedConnection->SessionTable;
|
||
ASSERT( tableHeader->Table[0].Owner == NULL );
|
||
|
||
uidIndex = 0;
|
||
|
||
//
|
||
// Remove the UID slot from the free list and set its owner and
|
||
// sequence number. Create a UID for the session. Increment
|
||
// count of sessions.
|
||
//
|
||
|
||
entry = &tableHeader->Table[uidIndex];
|
||
|
||
tableHeader->FirstFreeEntry = entry->NextFreeEntry;
|
||
DEBUG entry->NextFreeEntry = -2;
|
||
if ( tableHeader->LastFreeEntry == uidIndex ) {
|
||
tableHeader->LastFreeEntry = -1;
|
||
}
|
||
|
||
entry->Owner = session;
|
||
|
||
INCREMENT_UID_SEQUENCE( entry->SequenceNumber );
|
||
if ( uidIndex == 0 && entry->SequenceNumber == 0 ) {
|
||
INCREMENT_UID_SEQUENCE( entry->SequenceNumber );
|
||
}
|
||
session->Uid = MAKE_UID( uidIndex, entry->SequenceNumber );
|
||
|
||
pagedConnection->CurrentNumberOfSessions++;
|
||
|
||
IF_SMB_DEBUG(ADMIN1) {
|
||
KdPrint(( "Found UID. Index = 0x%lx, sequence = 0x%lx\n",
|
||
(ULONG)UID_INDEX( session->Uid ),
|
||
(ULONG)UID_SEQUENCE( session->Uid ) ));
|
||
}
|
||
|
||
//
|
||
// Insert the session on the global session list.
|
||
//
|
||
|
||
SrvInsertEntryOrderedList( &SrvSessionList, session );
|
||
|
||
//
|
||
// Reference the connection block to account for the new
|
||
// session.
|
||
//
|
||
|
||
SrvReferenceConnection( connection );
|
||
session->Connection = connection;
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
RELEASE_LOCK( SrvSessionList.Lock );
|
||
|
||
//
|
||
// Session successfully created. Remember its address in the
|
||
// work context block.
|
||
//
|
||
// *** Note that the reference count on the session block is
|
||
// initially set to 2, to allow for the active status on the
|
||
// block and the pointer that we're maintaining. In other
|
||
// words, this is a referenced pointer, and the pointer must
|
||
// be dereferenced when processing of this SMB is complete.
|
||
//
|
||
|
||
WorkContext->Session = session;
|
||
|
||
didLogon = TRUE;
|
||
|
||
}
|
||
|
||
//
|
||
// Try to match pathname against available shared resources. Note
|
||
// that if SrvVerifyShare finds a matching share, it references it
|
||
// and stores its address in WorkContext->Share.
|
||
//
|
||
|
||
share = SrvVerifyShare(
|
||
WorkContext,
|
||
(PSZ)request->Buffer + 1,
|
||
service,
|
||
SMB_IS_UNICODE( WorkContext ),
|
||
session->IsNullSession,
|
||
&status,
|
||
NULL
|
||
);
|
||
|
||
//
|
||
// If no match was found, return an error.
|
||
//
|
||
|
||
if ( share == NULL ) {
|
||
|
||
if ( didLogon ) {
|
||
SrvCloseSession( session );
|
||
}
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: SrvVerifyShare failed for %s. Status = %x\n", request->Buffer+1, status ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Impersonate the user so that we can capture his security context.
|
||
// This is necessary in order to determine whether the user can
|
||
// connect to the share.
|
||
//
|
||
|
||
status = IMPERSONATE( WorkContext );
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
SrvSetSmbError( WorkContext, status );
|
||
if ( didLogon ) {
|
||
SrvCloseSession( session );
|
||
}
|
||
SrvFreeTreeConnect( treeConnect );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SeCaptureSubjectContext( &subjectContext );
|
||
|
||
//
|
||
// Set up the desired access on the share, based on whether the
|
||
// server is paused. If the server is paused, admin privilege is
|
||
// required to connect to any share; if the server is not paused,
|
||
// admin privilege is required only for admin shares (C$, etc.).
|
||
//
|
||
|
||
if ( SrvPaused ) {
|
||
desiredAccess = SRVSVC_PAUSED_SHARE_CONNECT;
|
||
} else {
|
||
desiredAccess = SRVSVC_SHARE_CONNECT;
|
||
}
|
||
|
||
//
|
||
// Check whether the user has access to this share.
|
||
//
|
||
|
||
if ( !SeAccessCheck(
|
||
share->SecurityDescriptor,
|
||
&subjectContext,
|
||
FALSE,
|
||
desiredAccess,
|
||
0L,
|
||
NULL,
|
||
&SrvShareConnectMapping,
|
||
UserMode,
|
||
&grantedAccess,
|
||
&status
|
||
) ) {
|
||
|
||
IF_SMB_DEBUG(TREE2) {
|
||
KdPrint(( "SrvSmbTreeConnect: SeAccessCheck failed: %X\n",
|
||
status ));
|
||
}
|
||
|
||
//
|
||
// Release the subject context and revert to the server's security
|
||
// context.
|
||
//
|
||
|
||
SeReleaseSubjectContext( &subjectContext );
|
||
|
||
REVERT( );
|
||
|
||
if ( SrvPaused ) {
|
||
SrvSetSmbError( WorkContext, STATUS_SHARING_PAUSED );
|
||
status = STATUS_SHARING_PAUSED;
|
||
} else {
|
||
SrvSetSmbError( WorkContext, status );
|
||
}
|
||
|
||
if ( didLogon ) {
|
||
SrvCloseSession( session );
|
||
}
|
||
SrvFreeTreeConnect( treeConnect );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
ASSERT( grantedAccess == desiredAccess );
|
||
|
||
//
|
||
// Release the subject context and revert to the server's security
|
||
// context.
|
||
//
|
||
|
||
SeReleaseSubjectContext( &subjectContext );
|
||
|
||
REVERT( );
|
||
|
||
|
||
//
|
||
// Let the license server know
|
||
//
|
||
if( share->ShareType != ShareTypePipe ) {
|
||
|
||
status = SrvXsLSOperation( session, XACTSRV_MESSAGE_LSREQUEST );
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
if ( didLogon ) {
|
||
SrvCloseSession( session );
|
||
}
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: License server returned %X\n",
|
||
status ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Making a new tree connect visible is a three-step operation. It
|
||
// must be inserted in the containing share's tree connect list, the
|
||
// global ordered tree connect list, and the containing connection's
|
||
// tree connect table. We need to make these operations appear
|
||
// atomic, so that the tree connect cannot be accessed elsewhere
|
||
// before we're done setting it up. In order to do this, we hold
|
||
// all necessary locks the entire time we're doing the three
|
||
// operations. The first and second operations are protected by the
|
||
// global share lock (SrvShareLock), while the third operation is
|
||
// protected by the per-connection lock. We take out the share lock
|
||
// first, then the connection lock. This ordering is required by
|
||
// lock levels (see lock.h).
|
||
//
|
||
// Another problem here is that the checking of the share state, the
|
||
// inserting of the tree connect on the share's list, and the
|
||
// referencing of the share all need to be atomic. (The same holds
|
||
// for the connection actions.) Normally this would not be a
|
||
// problem, because we could just hold the share lock while doing
|
||
// all three actions. However, in this case we also need to hold
|
||
// the connection lock, and we can't call SrvReferenceShare while
|
||
// doing that. To get around this problem, we reference the share
|
||
// _before_ taking out the locks, and dereference after releasing
|
||
// the locks if we decide not to insert the tree connect.
|
||
//
|
||
|
||
status = SrvReferenceShareForTreeConnect( share );
|
||
|
||
//
|
||
// SrvReferenceShareForTreeConnect will fail if it cannot open the
|
||
// share root directory for some reason. If this happens,
|
||
// fail the tree connect attempt.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
|
||
if ( didLogon ) {
|
||
SrvCloseSession( session );
|
||
}
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: open of share root failed:%X\n",
|
||
status ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
ACQUIRE_LOCK( &SrvShareLock );
|
||
ASSERT( SrvTreeConnectList.Lock == &SrvShareLock );
|
||
ACQUIRE_LOCK( &connection->Lock );
|
||
|
||
//
|
||
// We first check all conditions to make sure that we can actually
|
||
// insert this tree connect block.
|
||
//
|
||
// Make sure that the share isn't closing, and that there aren't
|
||
// already too many uses on this share.
|
||
//
|
||
|
||
if ( GET_BLOCK_STATE(share) != BlockStateActive ) {
|
||
|
||
//
|
||
// The share is closing. Reject the request.
|
||
//
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: Share %wZ (0x%p) is closing\n",
|
||
&share->ShareName, share ));
|
||
}
|
||
|
||
status = STATUS_INVALID_PARAMETER;
|
||
goto cant_insert;
|
||
|
||
}
|
||
|
||
if ( share->CurrentUses > share->MaxUses ) {
|
||
|
||
//
|
||
// The share is full. Reject the request.
|
||
//
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: No more uses available for share %wZ (0x%p), max = %ld\n",
|
||
&share->ShareName, share, share->MaxUses ));
|
||
}
|
||
|
||
status = STATUS_REQUEST_NOT_ACCEPTED;
|
||
goto cant_insert;
|
||
|
||
}
|
||
|
||
//
|
||
// Make sure that the connection isn't closing.
|
||
//
|
||
|
||
if ( GET_BLOCK_STATE(connection) != BlockStateActive ) {
|
||
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: Connection closing\n" ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_INVALID_PARAMETER );
|
||
status = STATUS_INVALID_PARAMETER;
|
||
goto cant_insert;
|
||
|
||
}
|
||
|
||
//
|
||
// Find a TID that can be used for this tree connect.
|
||
//
|
||
|
||
tableHeader = &pagedConnection->TreeConnectTable;
|
||
if ( tableHeader->FirstFreeEntry == -1
|
||
&&
|
||
SrvGrowTable(
|
||
tableHeader,
|
||
SrvInitialTreeTableSize,
|
||
SrvMaxTreeTableSize,
|
||
&TableStatus ) == FALSE
|
||
) {
|
||
|
||
//
|
||
// No free entries in the tree table. Reject the request.
|
||
//
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: No more TIDs available.\n" ));
|
||
}
|
||
|
||
status = TableStatus;
|
||
|
||
if( TableStatus == STATUS_INSUFF_SERVER_RESOURCES )
|
||
{
|
||
SrvLogTableFullError( SRV_TABLE_TREE_CONNECT );
|
||
}
|
||
goto cant_insert;
|
||
|
||
}
|
||
|
||
tidIndex = tableHeader->FirstFreeEntry;
|
||
|
||
//
|
||
// All conditions have been satisfied. We can now do the things
|
||
// necessary to make the tree connect visible.
|
||
//
|
||
// Increment the count of uses for the share. Link the tree connect
|
||
// into the list of active tree connects for the share. Save the
|
||
// share address in the tree connect. Note that we referenced the
|
||
// share earlier, before taking out the connection lock.
|
||
//
|
||
|
||
SrvInsertTailList(
|
||
&share->TreeConnectList,
|
||
&treeConnect->ShareListEntry
|
||
);
|
||
|
||
treeConnect->Share = share;
|
||
|
||
//
|
||
// Remove the TID slot from the free list and set its owner and
|
||
// sequence number. Create a TID for the tree connect.
|
||
//
|
||
|
||
entry = &tableHeader->Table[tidIndex];
|
||
|
||
tableHeader->FirstFreeEntry = entry->NextFreeEntry;
|
||
DEBUG entry->NextFreeEntry = -2;
|
||
if ( tableHeader->LastFreeEntry == tidIndex ) {
|
||
tableHeader->LastFreeEntry = -1;
|
||
}
|
||
|
||
entry->Owner = treeConnect;
|
||
|
||
INCREMENT_TID_SEQUENCE( entry->SequenceNumber );
|
||
if ( tidIndex == 0 && entry->SequenceNumber == 0 ) {
|
||
INCREMENT_TID_SEQUENCE( entry->SequenceNumber );
|
||
}
|
||
treeConnect->Tid = MAKE_TID( tidIndex, entry->SequenceNumber );
|
||
|
||
IF_SMB_DEBUG(TREE1) {
|
||
KdPrint(( "Found TID. Index = 0x%lx, sequence = 0x%lx\n",
|
||
TID_INDEX( treeConnect->Tid ),
|
||
TID_SEQUENCE( treeConnect->Tid ) ));
|
||
}
|
||
|
||
//
|
||
// Reference the connection to account for the active tree connect.
|
||
//
|
||
|
||
SrvReferenceConnection( connection );
|
||
treeConnect->Connection = connection;
|
||
|
||
if( session )
|
||
{
|
||
SrvReferenceSession( session );
|
||
treeConnect->Session = session;
|
||
}
|
||
|
||
//
|
||
// Link the tree connect into the global list of tree connects.
|
||
//
|
||
|
||
SrvInsertEntryOrderedList( &SrvTreeConnectList, treeConnect );
|
||
|
||
//
|
||
// Release the locks used to make this operation appear atomic.
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
RELEASE_LOCK( &SrvShareLock );
|
||
|
||
//
|
||
// Get the qos information for this connection
|
||
//
|
||
|
||
SrvUpdateVcQualityOfService ( connection, NULL );
|
||
|
||
//
|
||
// Tree connect successfully created. Because the tree connect was
|
||
// created with an initial reference count of 2, dereference it now.
|
||
//
|
||
// *** Don't bother to save the tree connect address in the work
|
||
// context block, because we're going to forget our pointers
|
||
// soon anyway (we're done with the request). TreeConnectAndX
|
||
// has to remember these things, though.
|
||
//
|
||
|
||
SrvDereferenceTreeConnect( treeConnect );
|
||
|
||
//
|
||
// Set up response SMB.
|
||
//
|
||
|
||
SmbPutAlignedUshort( &WorkContext->ResponseHeader->Tid, treeConnect->Tid );
|
||
|
||
response->WordCount = 2;
|
||
SmbPutUshort( &response->MaxBufferSize, (USHORT)session->MaxBufferSize );
|
||
SmbPutUshort( &response->Tid, treeConnect->Tid );
|
||
SmbPutUshort( &response->ByteCount, 0 );
|
||
|
||
WorkContext->ResponseParameters = NEXT_LOCATION(
|
||
response,
|
||
RESP_TREE_CONNECT,
|
||
0
|
||
);
|
||
|
||
IF_DEBUG(TRACE2) KdPrint(( "SrvSmbTreeConnect complete.\n" ));
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
|
||
cant_insert:
|
||
|
||
//
|
||
// We get here if for some reason we decide that we can't insert
|
||
// the tree connect. On entry, status contains the reason code.
|
||
// The connection lock and the share lock are held.
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
RELEASE_LOCK( &SrvShareLock );
|
||
|
||
if ( didLogon ) {
|
||
SrvCloseSession( session );
|
||
}
|
||
|
||
SrvDereferenceShareForTreeConnect( share );
|
||
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
|
||
Cleanup:
|
||
SrvWmiEndContext(WorkContext);
|
||
return SmbStatus;
|
||
} // SrvSmbTreeConnect
|
||
|
||
|
||
SMB_PROCESSOR_RETURN_TYPE
|
||
SrvSmbTreeConnectAndX (
|
||
SMB_PROCESSOR_PARAMETERS
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Processes a tree connect and X SMB.
|
||
|
||
Arguments:
|
||
|
||
SMB_PROCESSOR_PARAMETERS - See smbprocs.h for a description
|
||
of the parameters to SMB processor routines.
|
||
|
||
Return Value:
|
||
|
||
SMB_PROCESSOR_RETURN_TYPE - See smbprocs.h
|
||
|
||
--*/
|
||
|
||
{
|
||
|
||
PREQ_TREE_CONNECT_ANDX request;
|
||
PRESP_TREE_CONNECT_ANDX response;
|
||
PRESP_EXTENDED_TREE_CONNECT_ANDX responseExtended;
|
||
PRESP_21_TREE_CONNECT_ANDX response21;
|
||
|
||
NTSTATUS status = STATUS_SUCCESS;
|
||
NTSTATUS TableStatus;
|
||
SMB_STATUS SmbStatus = SmbStatusInProgress;
|
||
PCONNECTION connection;
|
||
PPAGED_CONNECTION pagedConnection;
|
||
PTABLE_HEADER tableHeader;
|
||
PTABLE_ENTRY entry;
|
||
SHORT tidIndex;
|
||
PSHARE share;
|
||
PTREE_CONNECT treeConnect;
|
||
PVOID shareName;
|
||
PUCHAR shareType;
|
||
USHORT shareNameLength;
|
||
USHORT reqAndXOffset;
|
||
UCHAR nextCommand;
|
||
PSZ shareString;
|
||
USHORT shareStringLength;
|
||
USHORT RequestFlags;
|
||
USHORT byteCount;
|
||
PUCHAR smbBuffer;
|
||
PSESSION session;
|
||
SECURITY_SUBJECT_CONTEXT subjectContext;
|
||
ACCESS_MASK desiredAccess;
|
||
ACCESS_MASK grantedAccess;
|
||
BOOLEAN isUnicode;
|
||
UNICODE_STRING serverName;
|
||
BOOLEAN remapPipeNames = FALSE;
|
||
|
||
PAGED_CODE( );
|
||
if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT)
|
||
WorkContext->PreviousSMB = EVENT_TYPE_SMB_TREE_CONNECT_AND_X;
|
||
SrvWmiStartContext(WorkContext);
|
||
|
||
IF_SMB_DEBUG(TREE1) {
|
||
KdPrint(( "Tree connect and X request header at 0x%p, response header at 0x%p\n",
|
||
WorkContext->RequestHeader, WorkContext->ResponseHeader ));
|
||
KdPrint(( "Tree connect and X request parameters at 0x%p, response parameters at 0x%p\n",
|
||
WorkContext->RequestParameters,
|
||
WorkContext->ResponseParameters ));
|
||
}
|
||
|
||
//
|
||
// Set up parameters.
|
||
//
|
||
|
||
request = (PREQ_TREE_CONNECT_ANDX)(WorkContext->RequestParameters);
|
||
response = (PRESP_TREE_CONNECT_ANDX)(WorkContext->ResponseParameters);
|
||
responseExtended = (PRESP_EXTENDED_TREE_CONNECT_ANDX)(WorkContext->ResponseParameters);
|
||
response21 = (PRESP_21_TREE_CONNECT_ANDX)(WorkContext->ResponseParameters);
|
||
|
||
//
|
||
// If bit 0 of Flags is set, disconnect tree in header TID. We must
|
||
// get the appropriate tree connect pointer. SrvVerifyTid does this
|
||
// for us, referencing the tree connect and storing the pointer in
|
||
// the work context block. We have to dereference the block and
|
||
// erase the pointer after calling SrvCloseTreeConnect.
|
||
//
|
||
|
||
if ( (SmbGetUshort( &request->Flags ) & 1) != 0 ) {
|
||
|
||
if ( SrvVerifyTid(
|
||
WorkContext,
|
||
SmbGetAlignedUshort( &WorkContext->RequestHeader->Tid )
|
||
) == NULL ) {
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: Invalid TID to disconnect: 0x%lx\n",
|
||
SmbGetAlignedUshort( &WorkContext->RequestHeader->Tid ) ));
|
||
}
|
||
|
||
//
|
||
// Just ignore an invalid TID--this is what the LM 2.0
|
||
// server does.
|
||
//
|
||
|
||
} else {
|
||
|
||
SrvCloseTreeConnect( WorkContext->TreeConnect );
|
||
|
||
SrvDereferenceTreeConnect( WorkContext->TreeConnect );
|
||
WorkContext->TreeConnect = NULL;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//
|
||
// Validate the UID in the header and get a session pointer. We need
|
||
// the user's token to check whether they can access this share.
|
||
//
|
||
|
||
session = SrvVerifyUid(
|
||
WorkContext,
|
||
SmbGetAlignedUshort( &WorkContext->RequestHeader->Uid )
|
||
);
|
||
|
||
//
|
||
// If we couldn't find a valid session fail the tree connect.
|
||
//
|
||
|
||
if ( session == NULL ) {
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: rejecting tree connect for "
|
||
"session %p due to server paused.\n", session ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_SMB_BAD_UID );
|
||
status = STATUS_SMB_BAD_UID;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
else if( session->IsSessionExpired )
|
||
{
|
||
status = SESSION_EXPIRED_STATUS_CODE;
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Try to match pathname against available shared resources. Note
|
||
// that if SrvVerifyShare finds a matching share, it references it
|
||
// and stores its address in WorkContext->Share.
|
||
//
|
||
|
||
shareName = (PSZ)request->Buffer +
|
||
SmbGetUshort( &request->PasswordLength );
|
||
|
||
connection = WorkContext->Connection;
|
||
pagedConnection = connection->PagedConnection;
|
||
|
||
isUnicode = SMB_IS_UNICODE( WorkContext );
|
||
|
||
if ( isUnicode ) {
|
||
shareName = ALIGN_SMB_WSTR( shareName );
|
||
}
|
||
|
||
shareNameLength = SrvGetStringLength(
|
||
shareName,
|
||
END_OF_REQUEST_SMB( WorkContext ),
|
||
SMB_IS_UNICODE( WorkContext ),
|
||
TRUE // include null terminator
|
||
);
|
||
|
||
//
|
||
// if share name is bogus, return an error.
|
||
//
|
||
|
||
if ( shareNameLength == (USHORT)-1 ) {
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: pathname is bogus.\n"));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_BAD_NETWORK_NAME );
|
||
status = STATUS_BAD_NETWORK_NAME;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
shareType = (PCHAR)shareName + shareNameLength;
|
||
|
||
share = SrvVerifyShare(
|
||
WorkContext,
|
||
shareName,
|
||
shareType,
|
||
isUnicode,
|
||
session->IsNullSession,
|
||
&status,
|
||
&serverName
|
||
);
|
||
|
||
//
|
||
// If no match was found, return an error.
|
||
//
|
||
|
||
if ( share == NULL ) {
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: pathname does not match "
|
||
"any shares: %s\n", shareName ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If the the client is connecting with a netbiosless transport and the name of the
|
||
// server which the client was requesting doesn't match any of our servernames, then
|
||
// the client has accidentally connected to the wrong server. Let the client know.
|
||
//
|
||
if( !SrvDisableStrictNameChecking &&
|
||
serverName.Buffer != NULL &&
|
||
connection->Endpoint->IsNoNetBios &&
|
||
SrvIsDottedQuadAddress( &serverName ) == FALSE &&
|
||
SrvFindNamedEndpoint( &serverName, NULL ) == FALSE ) {
|
||
|
||
BOOL bBadName = TRUE;
|
||
|
||
// Last check, make sure its not the domain DNS name (which may differ from the NETBIOS DNS name)
|
||
ACQUIRE_LOCK_SHARED( &SrvEndpointLock );
|
||
|
||
// We only check up to the first ., so ntdev.microsoft.com would match SrvDnsDomainName "NTDEV"
|
||
// Strip off the excess info for the check, then put it back
|
||
if( SrvDnsDomainName ) {
|
||
if( SrvDnsDomainName->Length <= serverName.Length )
|
||
{
|
||
USHORT oldLength = serverName.Length;
|
||
serverName.Length = SrvDnsDomainName->Length;
|
||
|
||
if( RtlEqualUnicodeString( &serverName, SrvDnsDomainName, TRUE ) )
|
||
{
|
||
bBadName = FALSE;
|
||
}
|
||
|
||
serverName.Length = oldLength;
|
||
}
|
||
}
|
||
|
||
RELEASE_LOCK( &SrvEndpointLock );
|
||
|
||
//
|
||
// The client has connected to this server in error--turn the client back!
|
||
//
|
||
if( bBadName )
|
||
{
|
||
SrvSetSmbError( WorkContext, STATUS_DUPLICATE_NAME );
|
||
status = STATUS_DUPLICATE_NAME;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Impersonate the user so that we can capture his security context.
|
||
// This is necessary in order to determine whether the user can
|
||
// connect to the share.
|
||
//
|
||
|
||
status = IMPERSONATE( WorkContext );
|
||
if( !NT_SUCCESS( status ) ) {
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
SeCaptureSubjectContext( &subjectContext );
|
||
|
||
//
|
||
// Set up the desired access on the share, based on whether the
|
||
// server is paused. If the server is paused, admin privilege is
|
||
// required to connect to any share; if the server is not paused,
|
||
// admin privilege is required only for admin shares (C$, etc.).
|
||
//
|
||
|
||
if ( SrvPaused ) {
|
||
desiredAccess = SRVSVC_PAUSED_SHARE_CONNECT;
|
||
} else {
|
||
desiredAccess = SRVSVC_SHARE_CONNECT;
|
||
}
|
||
|
||
//
|
||
// Check whether the user has access to this share.
|
||
//
|
||
|
||
if ( !SeAccessCheck(
|
||
share->SecurityDescriptor,
|
||
&subjectContext,
|
||
FALSE,
|
||
desiredAccess,
|
||
0L,
|
||
NULL,
|
||
&SrvShareConnectMapping,
|
||
UserMode,
|
||
&grantedAccess,
|
||
&status
|
||
) ) {
|
||
|
||
IF_SMB_DEBUG(TREE2) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: SeAccessCheck failed: %X\n",
|
||
status ));
|
||
}
|
||
|
||
//
|
||
// Release the subject context and revert to the server's security
|
||
// context.
|
||
//
|
||
|
||
SeReleaseSubjectContext( &subjectContext );
|
||
|
||
REVERT( );
|
||
|
||
if ( SrvPaused ) {
|
||
SrvSetSmbError( WorkContext, STATUS_SHARING_PAUSED );
|
||
status = STATUS_SHARING_PAUSED;
|
||
} else {
|
||
SrvSetSmbError( WorkContext, status );
|
||
}
|
||
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
ASSERT( grantedAccess == desiredAccess );
|
||
|
||
//
|
||
// Release the subject context and revert to the server's security
|
||
// context.
|
||
//
|
||
|
||
SeReleaseSubjectContext( &subjectContext );
|
||
|
||
REVERT( );
|
||
|
||
//
|
||
// See if the license server wants to let this person in on the NTAS
|
||
//
|
||
if( share->ShareType != ShareTypePipe ) {
|
||
|
||
status = SrvXsLSOperation( session, XACTSRV_MESSAGE_LSREQUEST );
|
||
|
||
if( !NT_SUCCESS( status ) ) {
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: License server returned %X\n",
|
||
status ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
} else if( serverName.Buffer != NULL ) {
|
||
|
||
//
|
||
// This is the IPC$ share. See if we're supposed to remap pipe names
|
||
//
|
||
SrvFindNamedEndpoint( &serverName, &remapPipeNames );
|
||
|
||
}
|
||
|
||
//
|
||
// Allocate a tree connect block.
|
||
//
|
||
|
||
SrvAllocateTreeConnect( &treeConnect, serverName.Buffer ? &serverName : NULL );
|
||
|
||
if ( treeConnect == NULL ) {
|
||
|
||
//
|
||
// Unable to allocate tree connect. Return an error to the
|
||
// client.
|
||
//
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_INSUFF_SERVER_RESOURCES );
|
||
status = STATUS_INSUFF_SERVER_RESOURCES;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
treeConnect->RemapPipeNames = remapPipeNames;
|
||
|
||
//
|
||
// Making a new tree connect visible is a three-step operation. It
|
||
// must be inserted in the containing share's tree connect list, the
|
||
// global ordered tree connect list, and the containing connection's
|
||
// tree connect table. We need to make these operations appear
|
||
// atomic, so that the tree connect cannot be accessed elsewhere
|
||
// before we're done setting it up. In order to do this, we hold
|
||
// all necessary locks the entire time we're doing the three
|
||
// operations. The first and second operations are protected by the
|
||
// global share lock (SrvShareLock), while the third operation is
|
||
// protected by the per-connection lock. We take out the share lock
|
||
// first, then the connection lock. This ordering is required by
|
||
// lock levels (see lock.h).
|
||
//
|
||
// Another problem here is that the checking of the share state, the
|
||
// inserting of the tree connect on the share's list, and the
|
||
// referencing of the share all need to be atomic. (The same holds
|
||
// for the connection actions.) Normally this would not be a
|
||
// problem, because we could just hold the share lock while doing
|
||
// all three actions. However, in this case we also need to hold
|
||
// the connection lock, and we can't call SrvReferenceShare while
|
||
// doing that. To get around this problem, we reference the share
|
||
// _before_ taking out the locks, and dereference after releasing
|
||
// the locks if we decide not to insert the tree connect.
|
||
//
|
||
|
||
status = SrvReferenceShareForTreeConnect( share );
|
||
|
||
//
|
||
// SrvReferenceShareForTreeConnect will fail if it cannot open the
|
||
// share root directory for some reason. If this happens,
|
||
// fail the tree connect attempt.
|
||
//
|
||
|
||
if ( !NT_SUCCESS(status) ) {
|
||
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: open of share root failed:%X\n",
|
||
status ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
ACQUIRE_LOCK( &SrvShareLock );
|
||
ASSERT( SrvTreeConnectList.Lock == &SrvShareLock );
|
||
ACQUIRE_LOCK( &connection->Lock );
|
||
|
||
//
|
||
// We first check all conditions to make sure that we can actually
|
||
// insert this tree connect block.
|
||
//
|
||
// Make sure that the share isn't closing, and that there aren't
|
||
// already too many uses on this share.
|
||
//
|
||
|
||
if ( GET_BLOCK_STATE(share) != BlockStateActive ) {
|
||
|
||
//
|
||
// The share is closing. Reject the request.
|
||
//
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: Share %wZ (0x%p) is closing\n",
|
||
&share->ShareName, share ));
|
||
}
|
||
|
||
status = STATUS_INVALID_PARAMETER;
|
||
goto cant_insert;
|
||
|
||
}
|
||
|
||
if ( share->CurrentUses > share->MaxUses ) {
|
||
|
||
//
|
||
// The share is full. Reject the request.
|
||
//
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: No more uses available for share %wZ (0x%p), max = %ld\n",
|
||
&share->ShareName, share, share->MaxUses ));
|
||
}
|
||
|
||
status = STATUS_REQUEST_NOT_ACCEPTED;
|
||
goto cant_insert;
|
||
|
||
}
|
||
|
||
//
|
||
// Make sure that the connection isn't closing, and that there's
|
||
// room in its tree connect table.
|
||
//
|
||
|
||
if ( GET_BLOCK_STATE(connection) != BlockStateActive ) {
|
||
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: Connection closing\n" ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_INVALID_PARAMETER );
|
||
status = STATUS_INVALID_PARAMETER;
|
||
goto cant_insert;
|
||
|
||
}
|
||
|
||
//
|
||
// Find a TID that can be used for this tree connect.
|
||
//
|
||
|
||
tableHeader = &pagedConnection->TreeConnectTable;
|
||
if ( tableHeader->FirstFreeEntry == -1
|
||
&&
|
||
SrvGrowTable(
|
||
tableHeader,
|
||
SrvInitialTreeTableSize,
|
||
SrvMaxTreeTableSize,
|
||
&TableStatus ) == FALSE
|
||
) {
|
||
|
||
//
|
||
// No free entries in the tree table. Reject the request.
|
||
//
|
||
|
||
IF_DEBUG(ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnect: No more TIDs available.\n" ));
|
||
}
|
||
|
||
if( TableStatus == STATUS_INSUFF_SERVER_RESOURCES )
|
||
{
|
||
SrvLogTableFullError( SRV_TABLE_TREE_CONNECT );
|
||
}
|
||
|
||
status = TableStatus;
|
||
goto cant_insert;
|
||
|
||
}
|
||
|
||
tidIndex = tableHeader->FirstFreeEntry;
|
||
|
||
//
|
||
// All conditions have been satisfied. We can now do the things
|
||
// necessary to make the tree connect visible.
|
||
//
|
||
// Link the tree connect into the list of active tree connects for
|
||
// the share. Save the share address in the tree connect. Note
|
||
// that we referenced the share earlier, before taking out the
|
||
// connection lock.
|
||
//
|
||
|
||
SrvInsertTailList(
|
||
&share->TreeConnectList,
|
||
&treeConnect->ShareListEntry
|
||
);
|
||
|
||
treeConnect->Share = share;
|
||
|
||
//
|
||
// Remove the TID slot from the free list and set its owner and
|
||
// sequence number. Create a TID for the tree connect.
|
||
//
|
||
|
||
entry = &tableHeader->Table[tidIndex];
|
||
|
||
tableHeader->FirstFreeEntry = entry->NextFreeEntry;
|
||
DEBUG entry->NextFreeEntry = -2;
|
||
if ( tableHeader->LastFreeEntry == tidIndex ) {
|
||
tableHeader->LastFreeEntry = -1;
|
||
}
|
||
|
||
entry->Owner = treeConnect;
|
||
|
||
INCREMENT_TID_SEQUENCE( entry->SequenceNumber );
|
||
if ( tidIndex == 0 && entry->SequenceNumber == 0 ) {
|
||
INCREMENT_TID_SEQUENCE( entry->SequenceNumber );
|
||
}
|
||
treeConnect->Tid = MAKE_TID( tidIndex, entry->SequenceNumber );
|
||
|
||
IF_SMB_DEBUG(TREE1) {
|
||
KdPrint(( "Found TID. Index = 0x%lx, sequence = 0x%lx\n",
|
||
TID_INDEX( treeConnect->Tid ),
|
||
TID_SEQUENCE( treeConnect->Tid ) ));
|
||
}
|
||
|
||
//
|
||
// Reference the connection to account for the active tree connect.
|
||
//
|
||
|
||
SrvReferenceConnection( connection );
|
||
treeConnect->Connection = connection;
|
||
if( session )
|
||
{
|
||
SrvReferenceSession( session );
|
||
treeConnect->Session = session;
|
||
}
|
||
|
||
//
|
||
// Link the tree connect into the global list of tree connects.
|
||
//
|
||
|
||
SrvInsertEntryOrderedList( &SrvTreeConnectList, treeConnect );
|
||
|
||
//
|
||
// Release the locks used to make this operation appear atomic.
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
RELEASE_LOCK( &SrvShareLock );
|
||
|
||
//
|
||
// Get the qos information for this connection
|
||
//
|
||
|
||
SrvUpdateVcQualityOfService ( connection, NULL );
|
||
|
||
//
|
||
// Tree connect successfully created. Save the tree connect block
|
||
// address in the work context block. Note that the reference count
|
||
// on the new block was incremented on creation to account for our
|
||
// reference to the block.
|
||
//
|
||
|
||
WorkContext->TreeConnect = treeConnect;
|
||
|
||
//
|
||
// Set up response SMB, making sure to save request fields first in
|
||
// case the response overwrites the request.
|
||
//
|
||
|
||
reqAndXOffset = SmbGetUshort( &request->AndXOffset );
|
||
nextCommand = request->AndXCommand;
|
||
|
||
RequestFlags = SmbGetUshort(&request->Flags);
|
||
|
||
SmbPutAlignedUshort( &WorkContext->RequestHeader->Tid, treeConnect->Tid );
|
||
SmbPutAlignedUshort( &WorkContext->ResponseHeader->Tid, treeConnect->Tid );
|
||
|
||
response->AndXCommand = nextCommand;
|
||
response->AndXReserved = 0;
|
||
|
||
if ( connection->SmbDialect > SmbDialectDosLanMan21) {
|
||
response->WordCount = 2;
|
||
smbBuffer = (PUCHAR)response->Buffer;
|
||
} else {
|
||
if (RequestFlags & TREE_CONNECT_ANDX_EXTENDED_RESPONSE) {
|
||
responseExtended->WordCount = 7;
|
||
smbBuffer = (PUCHAR)responseExtended->Buffer;
|
||
} else {
|
||
response21->WordCount = 3;
|
||
smbBuffer = (PUCHAR)response21->Buffer;
|
||
}
|
||
|
||
// Fields common to 21 and extended response.
|
||
response21->OptionalSupport = SMB_SUPPORT_SEARCH_BITS;
|
||
|
||
SrvIsShareInDfs( share, &share->IsDfs, &share->IsDfsRoot );
|
||
|
||
if (share->IsDfs) {
|
||
response21->OptionalSupport |= SMB_SHARE_IS_IN_DFS;
|
||
}
|
||
|
||
switch( share->CSCState ) {
|
||
case CSC_CACHE_MANUAL_REINT:
|
||
response21->OptionalSupport |= SMB_CSC_CACHE_MANUAL_REINT;
|
||
break;
|
||
case CSC_CACHE_AUTO_REINT:
|
||
response21->OptionalSupport |= SMB_CSC_CACHE_AUTO_REINT;
|
||
break;
|
||
case CSC_CACHE_VDO:
|
||
response21->OptionalSupport |= SMB_CSC_CACHE_VDO;
|
||
break;
|
||
case CSC_CACHE_NONE:
|
||
response21->OptionalSupport |= SMB_CSC_NO_CACHING;
|
||
break;
|
||
}
|
||
|
||
if( share->UniqueNames )
|
||
{
|
||
response21->OptionalSupport |= SMB_UNIQUE_FILE_NAME;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Append the service name string to the SMB. The service name
|
||
// is always sent in ANSI.
|
||
//
|
||
|
||
shareString = StrShareTypeNames[share->ShareType];
|
||
shareStringLength = (USHORT)( strlen( shareString ) + 1 );
|
||
RtlCopyMemory ( smbBuffer, shareString, shareStringLength );
|
||
|
||
byteCount = shareStringLength;
|
||
smbBuffer += shareStringLength;
|
||
|
||
if ( connection->SmbDialect <= SmbDialectDosLanMan21 ) {
|
||
|
||
//
|
||
// Append the file system name to the response.
|
||
// If the file system name is unavailable, supply the nul string
|
||
// as the name.
|
||
//
|
||
|
||
if ( isUnicode ) {
|
||
|
||
if ( ((ULONG_PTR)smbBuffer & 1) != 0 ) {
|
||
smbBuffer++;
|
||
byteCount++;
|
||
}
|
||
|
||
if ( share->Type.FileSystem.Name.Buffer != NULL ) {
|
||
|
||
RtlCopyMemory(
|
||
smbBuffer,
|
||
share->Type.FileSystem.Name.Buffer,
|
||
share->Type.FileSystem.Name.Length
|
||
);
|
||
|
||
byteCount += share->Type.FileSystem.Name.Length;
|
||
|
||
} else {
|
||
|
||
*(PWCH)smbBuffer = UNICODE_NULL;
|
||
byteCount += sizeof( UNICODE_NULL );
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
if ( share->Type.FileSystem.Name.Buffer != NULL ) {
|
||
|
||
RtlCopyMemory(
|
||
smbBuffer,
|
||
share->Type.FileSystem.OemName.Buffer,
|
||
share->Type.FileSystem.OemName.Length
|
||
);
|
||
|
||
byteCount += share->Type.FileSystem.OemName.Length;
|
||
|
||
} else {
|
||
|
||
*(PUCHAR)smbBuffer = '\0';
|
||
byteCount += 1;
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
if (RequestFlags & TREE_CONNECT_ANDX_EXTENDED_RESPONSE) {
|
||
PRESP_EXTENDED_TREE_CONNECT_ANDX ExtendedResponse;
|
||
|
||
ExtendedResponse = (PRESP_EXTENDED_TREE_CONNECT_ANDX)response;
|
||
|
||
SmbPutUshort( &ExtendedResponse->ByteCount, byteCount );
|
||
|
||
SrvUpdateMaximalShareAccessRightsInResponse(
|
||
WorkContext,
|
||
&ExtendedResponse->MaximalShareAccessRights,
|
||
&ExtendedResponse->GuestMaximalShareAccessRights);
|
||
|
||
SmbPutUshort(
|
||
&ExtendedResponse->AndXOffset,
|
||
GET_ANDX_OFFSET(
|
||
WorkContext->ResponseHeader,
|
||
WorkContext->ResponseParameters,
|
||
RESP_EXTENDED_TREE_CONNECT_ANDX,
|
||
byteCount
|
||
)
|
||
);
|
||
} else {
|
||
SmbPutUshort( &response21->ByteCount, byteCount );
|
||
|
||
SmbPutUshort(
|
||
&response->AndXOffset,
|
||
GET_ANDX_OFFSET(
|
||
WorkContext->ResponseHeader,
|
||
WorkContext->ResponseParameters,
|
||
RESP_21_TREE_CONNECT_ANDX,
|
||
byteCount
|
||
)
|
||
);
|
||
}
|
||
} else { // if Smb dialect == LAN Man 2.1
|
||
SmbPutUshort( &response->ByteCount, byteCount );
|
||
|
||
SmbPutUshort(
|
||
&response->AndXOffset,
|
||
GET_ANDX_OFFSET(
|
||
WorkContext->ResponseHeader,
|
||
WorkContext->ResponseParameters,
|
||
RESP_TREE_CONNECT_ANDX,
|
||
byteCount
|
||
)
|
||
);
|
||
}
|
||
|
||
|
||
WorkContext->ResponseParameters = (PUCHAR)WorkContext->ResponseHeader +
|
||
SmbGetUshort( &response->AndXOffset );
|
||
|
||
//
|
||
// Test for legal followon command.
|
||
//
|
||
|
||
switch ( nextCommand ) {
|
||
case SMB_COM_NO_ANDX_COMMAND:
|
||
break;
|
||
|
||
case SMB_COM_OPEN:
|
||
case SMB_COM_OPEN_ANDX:
|
||
case SMB_COM_CREATE:
|
||
case SMB_COM_CREATE_NEW:
|
||
case SMB_COM_CREATE_DIRECTORY:
|
||
case SMB_COM_DELETE:
|
||
case SMB_COM_DELETE_DIRECTORY:
|
||
case SMB_COM_SEARCH:
|
||
case SMB_COM_FIND:
|
||
case SMB_COM_FIND_UNIQUE:
|
||
case SMB_COM_COPY:
|
||
case SMB_COM_RENAME:
|
||
case SMB_COM_NT_RENAME:
|
||
case SMB_COM_CHECK_DIRECTORY:
|
||
case SMB_COM_QUERY_INFORMATION:
|
||
case SMB_COM_SET_INFORMATION:
|
||
case SMB_COM_QUERY_INFORMATION_SRV:
|
||
case SMB_COM_OPEN_PRINT_FILE:
|
||
case SMB_COM_GET_PRINT_QUEUE:
|
||
case SMB_COM_TRANSACTION:
|
||
//
|
||
// Make sure the AndX command is still within the received SMB
|
||
//
|
||
if( (PCHAR)WorkContext->RequestHeader + reqAndXOffset <=
|
||
END_OF_REQUEST_SMB( WorkContext ) ) {
|
||
break;
|
||
}
|
||
|
||
/* Falls Through */
|
||
|
||
default: // Illegal followon command
|
||
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SrvSmbTreeConnectAndX: Illegal followon command: 0x%c\n", nextCommand ));
|
||
}
|
||
|
||
SrvLogInvalidSmb( WorkContext );
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_INVALID_SMB );
|
||
status = STATUS_INVALID_SMB;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// If there is an AndX command, set up to process it. Otherwise,
|
||
// indicate completion to the caller.
|
||
//
|
||
|
||
if ( nextCommand != SMB_COM_NO_ANDX_COMMAND ) {
|
||
|
||
// *** Watch out for overwriting request with response.
|
||
|
||
WorkContext->NextCommand = nextCommand;
|
||
|
||
WorkContext->RequestParameters = (PUCHAR)WorkContext->RequestHeader +
|
||
reqAndXOffset;
|
||
|
||
SmbStatus = SmbStatusMoreCommands;
|
||
goto Cleanup;
|
||
}
|
||
|
||
IF_DEBUG(TRACE2) KdPrint(( "SrvSmbTreeConnectAndX complete.\n" ));
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
|
||
cant_insert:
|
||
|
||
//
|
||
// We get here if for some reason we decide that we can't insert
|
||
// the tree connect. On entry, status contains the reason code.
|
||
// The connection lock and the share lock are held.
|
||
//
|
||
|
||
RELEASE_LOCK( &connection->Lock );
|
||
RELEASE_LOCK( &SrvShareLock );
|
||
|
||
SrvDereferenceShareForTreeConnect( share );
|
||
|
||
SrvFreeTreeConnect( treeConnect );
|
||
|
||
SrvSetSmbError( WorkContext, status );
|
||
SmbStatus = SmbStatusSendResponse;
|
||
|
||
Cleanup:
|
||
SrvWmiEndContext(WorkContext);
|
||
return SmbStatus;
|
||
} // SrvSmbTreeConnectAndX
|
||
|
||
|
||
SMB_PROCESSOR_RETURN_TYPE
|
||
SrvSmbTreeDisconnect (
|
||
SMB_PROCESSOR_PARAMETERS
|
||
)
|
||
|
||
/*++
|
||
|
||
Routine Description:
|
||
|
||
Processes a tree disconnect SMB.
|
||
|
||
Arguments:
|
||
|
||
SMB_PROCESSOR_PARAMETERS - See smbprocs.h for a description
|
||
of the parameters to SMB processor routines.
|
||
|
||
Return Value:
|
||
|
||
SMB_PROCESSOR_RETURN_TYPE - See smbprocs.h
|
||
|
||
--*/
|
||
|
||
{
|
||
PREQ_TREE_DISCONNECT request;
|
||
PRESP_TREE_DISCONNECT response;
|
||
PTREE_CONNECT treeConnect;
|
||
NTSTATUS status = STATUS_SUCCESS;
|
||
SMB_STATUS SmbStatus = SmbStatusInProgress;
|
||
|
||
PAGED_CODE( );
|
||
if (WorkContext->PreviousSMB == EVENT_TYPE_SMB_LAST_EVENT)
|
||
WorkContext->PreviousSMB = EVENT_TYPE_SMB_TREE_DISCONNECT;
|
||
SrvWmiStartContext(WorkContext);
|
||
|
||
IF_SMB_DEBUG(TREE1) {
|
||
KdPrint(( "Tree disconnect request header at 0x%p, response header at 0x%p\n",
|
||
WorkContext->RequestHeader, WorkContext->ResponseHeader ));
|
||
KdPrint(( "Tree disconnect request parameters at 0x%p, response parameters at 0x%p\n",
|
||
WorkContext->RequestParameters,
|
||
WorkContext->ResponseParameters ));
|
||
}
|
||
|
||
//
|
||
// Set up parameters.
|
||
//
|
||
|
||
request = (PREQ_TREE_DISCONNECT)(WorkContext->RequestParameters);
|
||
response = (PRESP_TREE_DISCONNECT)(WorkContext->ResponseParameters);
|
||
|
||
//
|
||
// Find tree connect corresponding to given TID if a tree connect
|
||
// pointer has not already been put in the WorkContext block by an
|
||
// AndX command.
|
||
//
|
||
|
||
treeConnect = SrvVerifyTid(
|
||
WorkContext,
|
||
SmbGetAlignedUshort( &WorkContext->RequestHeader->Tid )
|
||
);
|
||
|
||
if ( treeConnect == NULL ) {
|
||
|
||
IF_DEBUG(SMB_ERRORS) {
|
||
KdPrint(( "SrvSmbTreeDisconnect: Invalid TID: 0x%lx\n",
|
||
SmbGetAlignedUshort( &WorkContext->RequestHeader->Tid ) ));
|
||
}
|
||
|
||
SrvSetSmbError( WorkContext, STATUS_SMB_BAD_TID );
|
||
status = STATUS_SMB_BAD_UID;
|
||
SmbStatus = SmbStatusSendResponse;
|
||
goto Cleanup;
|
||
}
|
||
|
||
//
|
||
// Do the actual tree disconnect.
|
||
//
|
||
|
||
SrvCloseTreeConnect( WorkContext->TreeConnect );
|
||
|
||
//
|
||
// Build the response SMB.
|
||
//
|
||
|
||
response->WordCount = 0;
|
||
SmbPutUshort( &response->ByteCount, 0 );
|
||
|
||
WorkContext->ResponseParameters = NEXT_LOCATION(
|
||
response,
|
||
RESP_TREE_DISCONNECT,
|
||
0
|
||
);
|
||
SmbStatus = SmbStatusSendResponse;
|
||
IF_DEBUG(TRACE2) KdPrint(( "SrvSmbTreeDisconnect complete.\n" ));
|
||
|
||
Cleanup:
|
||
SrvWmiEndContext(WorkContext);
|
||
return SmbStatus;
|
||
|
||
} // SrvSmbTreeDisconnect
|
||
|