/*++ 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