mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8056 lines
226 KiB
8056 lines
226 KiB
/*++
|
|
|
|
Copyright (c) 1990 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
connect.c
|
|
|
|
Abstract:
|
|
|
|
This module defines the routines that manage connection package in the
|
|
NT redirector.
|
|
|
|
|
|
Author:
|
|
|
|
Larry Osterman (LarryO) 1-Jun-1990
|
|
|
|
|
|
Some naming convention notes:
|
|
|
|
In general, routines this module are structured as follows:
|
|
|
|
A "Connection" refers to an established ConnectList/ServerList pair.
|
|
|
|
A "ConnectList" refers to an object of type ConnectList.
|
|
|
|
A "ServerList" refers to an object of type ServerList.
|
|
|
|
A "Create" routine will use a given create disposition to either find
|
|
or allocate a routine.
|
|
|
|
A "Find" routine looks up a structure.
|
|
A successful "Find" routine implicitly increments the structures
|
|
reference count.
|
|
|
|
An "Allocate" routine will create a new structure of a given type
|
|
It will NOT make any network connections, it will simply allocate
|
|
the structure.
|
|
|
|
A "Free" routine will free up an instance of a given type of
|
|
structure. It will not remove any network connections.
|
|
|
|
A "Reference" routine will increment the reference to a structure.
|
|
|
|
A "DeReference" routine will decrement the reference to a
|
|
structure. If the reference goes to zero, it will remove the
|
|
structure from it's appropriate list, and perform whatever
|
|
actions are nececcary to remove the structure including freeing
|
|
it's memory.
|
|
|
|
|
|
|
|
|
|
Revision History:
|
|
|
|
1-Jun-1990 LarryO
|
|
|
|
Created
|
|
|
|
--*/
|
|
|
|
#define INCLUDE_SMB_ADMIN
|
|
#define INCLUDE_SMB_TREE
|
|
|
|
#ifdef _CAIRO_
|
|
#define INCLUDE_SMB_TRANSACTION
|
|
#define INCLUDE_SMB_CAIRO
|
|
#endif // _CAIRO_
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
#ifdef _CAIRO_
|
|
#include <kerbcon.h> // For KERBSIZE_AP_REPLY
|
|
#endif
|
|
|
|
#ifdef RASAUTODIAL
|
|
extern BOOLEAN fAcdLoadedG;
|
|
|
|
VOID
|
|
RdrAttemptAutoDial(
|
|
PUNICODE_STRING Server
|
|
);
|
|
#endif // RASAUTODIAL
|
|
|
|
//BOOLEAN SmallSmbs = FALSE;
|
|
|
|
KSPIN_LOCK
|
|
RdrServerListSpinLock = {0};
|
|
|
|
//
|
|
//
|
|
//
|
|
// SMB exchanging context structures.
|
|
//
|
|
//
|
|
//
|
|
//
|
|
typedef struct _TreeConnectContext {
|
|
TRANCEIVE_HEADER Header; // Common context header info
|
|
NTSTATUS SessionSetupError; // Error of SessionSetup&X SMB
|
|
NTSTATUS TreeConnectError; // Error of TreeConnect&X SMB.
|
|
USHORT TreeId; // Tree ID for connection
|
|
USHORT UserId; // UserID for connection.
|
|
USHORT BufferSize; // MaxBufferSize on Core servers
|
|
ULONG Type; // Type of connection
|
|
ULONG ReceiveLength; // Number of bytes received
|
|
PMDL SessionSetupMdl; // MDL for session setup.
|
|
PIRP ReceiveIrp; // Receive I/O request packet.
|
|
KEVENT ReceiveCompleteEvent; // Event signalled when receive completes
|
|
BOOLEAN BufferTooShort; // True if buffer received was too short
|
|
BOOLEAN UseLmSessionSetupKey; // True if we are using the LM session setup key.
|
|
BOOLEAN ShareIsInDfs; // True if the share is in Dfs
|
|
BOOLEAN Unused; // For padding...
|
|
WCHAR FileSystemType[LM20_DEVLEN+1]; // Type of file system backing connect
|
|
} TREECONNECTCONTEXT, *PTREECONNECTCONTEXT;
|
|
|
|
|
|
typedef struct _NegotiateContext {
|
|
TRANCEIVE_HEADER Header;
|
|
LARGE_INTEGER ServerTime; // Local time on server.
|
|
ULONG SessionKey; // Session key (unique key identifying VC)
|
|
ULONG BufferSize; // Servers negotiated buffer size
|
|
ULONG MaxRawSize; // Servers maximum raw I/O size. (NT ONLY)
|
|
ULONG Capabilities; // Servers capabilities (NT ONLY)
|
|
USHORT DialectIndex; // Index of dialect negotiated
|
|
USHORT MaximumRequests; // Maximum number of requests server supports
|
|
USHORT MaximumVCs; // Maximum number of VC's per session
|
|
USHORT TimeZone; // Time zone at server
|
|
USHORT CryptKeyLength; // Size of encryption key.
|
|
BOOLEAN SupportsLockRead; // Server supports Lock&Read/Write&Unlock
|
|
//
|
|
// The following fields are only valid if MSNET 1.03 or better servers
|
|
//
|
|
BOOLEAN SupportsRawRead; // Server supports raw read
|
|
BOOLEAN SupportsRawWrite; // Server supports raw write
|
|
//
|
|
// The following fields are only valid for LANMAN 1.0 or better servers
|
|
//
|
|
BOOLEAN UserSecurity; // TRUE if user mode security, FALSE otherwise
|
|
BOOLEAN Encryption; // TRUE if server supports encryption.
|
|
|
|
|
|
UCHAR CryptKey[CRYPT_TXT_LEN]; // Password encryption key
|
|
UNICODE_STRING DomainName;
|
|
} NEGOTIATECONTEXT, *PNEGOTIATECONTEXT;
|
|
|
|
//
|
|
// Logon session termination context
|
|
//
|
|
|
|
typedef struct _LogonSessionTerminationContext {
|
|
WORK_HEADER WorkHeader;
|
|
LUID LogonId;
|
|
} LOGONSESSIONTERMINATIONCONTEXT, *PLOGONSESSIONTERMINATIONCONTEXT;
|
|
|
|
//
|
|
//
|
|
// Forward definitions of private routines used by the connection package
|
|
//
|
|
//
|
|
|
|
VOID
|
|
CleanupTransportConnection(
|
|
IN PIRP Irp,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSERVERLISTENTRY Server
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
FindConnection (
|
|
IN PUNICODE_STRING ServerName,
|
|
IN PUNICODE_STRING ShareName,
|
|
IN PTRANSPORT Transport,
|
|
OUT PCONNECTLISTENTRY *Connection,
|
|
IN ULONG Type
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
FindServerList (
|
|
IN PUNICODE_STRING ServerName,
|
|
IN PTRANSPORT Transport OPTIONAL,
|
|
OUT PSERVERLISTENTRY *ServerList
|
|
);
|
|
|
|
DBGSTATIC
|
|
VOID
|
|
FreeServerList (
|
|
IN PSERVERLISTENTRY ServerList
|
|
);
|
|
|
|
DBGSTATIC
|
|
VOID
|
|
FreeConnectList (
|
|
IN PCONNECTLISTENTRY CLE
|
|
);
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
AllocateConnectList (
|
|
IN PUNICODE_STRING RemoteName,
|
|
IN PSERVERLISTENTRY ServerList,
|
|
OUT PCONNECTLISTENTRY *CLE
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
AllocateServerList (
|
|
IN PUNICODE_STRING ServerName,
|
|
IN PTRANSPORT Transport,
|
|
OUT PSERVERLISTENTRY *ServerList
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
AllocateConnection (
|
|
IN PUNICODE_STRING ServerName,
|
|
IN PUNICODE_STRING ShareName,
|
|
IN PTRANSPORT Transport,
|
|
OUT PCONNECTLISTENTRY *Connection
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
CallServer (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se
|
|
);
|
|
|
|
DBGSTATIC
|
|
STANDARD_CALLBACK_HEADER (
|
|
NegotiateCallback
|
|
);
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
CreateTreeConnection (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN ULONG Type,
|
|
IN PSECURITY_ENTRY Se
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildCoreConnect(
|
|
IN PCONNECTLISTENTRY Connection,
|
|
OUT PSMB_BUFFER *Smb,
|
|
IN ULONG Type,
|
|
IN PSECURITY_ENTRY Se
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildLanmanConnect(
|
|
IN PCONNECTLISTENTRY Connection,
|
|
OUT PSMB_BUFFER *Smb,
|
|
IN ULONG Type,
|
|
IN PSECURITY_ENTRY Se,
|
|
OUT PBOOLEAN ServerNeedsSession,
|
|
OUT PBOOLEAN ConnectionNeedsTreeConnect
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildSessionSetupAndX(
|
|
IN PSMB_BUFFER Smb,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildTreeConnectAndX(
|
|
IN PSMB_BUFFER Smb,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se,
|
|
IN BOOLEAN CombiningAndX,
|
|
IN ULONG Type
|
|
);
|
|
|
|
DBGSTATIC
|
|
STANDARD_CALLBACK_HEADER (
|
|
CoreTreeConnectCallback
|
|
);
|
|
|
|
DBGSTATIC
|
|
STANDARD_CALLBACK_HEADER (
|
|
TreeConnectCallback
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
TreeConnectComplete (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PVOID Ctx
|
|
);
|
|
|
|
NTSTATUS
|
|
ProcessTreeConnectAndXBuffer(
|
|
IN PSMB_HEADER SmbStart,
|
|
IN PVOID p,
|
|
IN PTREECONNECTCONTEXT Context,
|
|
IN PSERVERLISTENTRY Server,
|
|
IN ULONG SmbLength,
|
|
IN ULONG ReceiveFlags
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
TreeDisconnect (
|
|
IN PIRP Irp,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se OPTIONAL,
|
|
IN PSERVERLISTENTRY Server
|
|
);
|
|
|
|
USHORT
|
|
GetTimeZone(
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
GetFcbReferences(
|
|
IN PFCB Fcb,
|
|
IN PVOID Ctx
|
|
);
|
|
VOID
|
|
EvaluateServerTimeouts(
|
|
IN PSERVERLISTENTRY Server,
|
|
IN PVOID Context
|
|
);
|
|
|
|
VOID
|
|
DelayedDereferenceServer(
|
|
IN PVOID Ctx
|
|
);
|
|
|
|
VOID
|
|
LogonSessionTerminationHandler(
|
|
IN PVOID Context
|
|
);
|
|
|
|
#ifdef _CAIRO_
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
SetupCairoSession (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se OPTIONAL
|
|
);
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildCairoSessionSetup(IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se,
|
|
IN PSERVERLISTENTRY Server,
|
|
IN PUCHAR pOldBlob,
|
|
IN ULONG cOldBlobSize,
|
|
OUT PVOID *SendData,
|
|
OUT PCLONG SendDataCount,
|
|
OUT PVOID *ReceiveData,
|
|
OUT PCLONG ReceiveDataCount);
|
|
#endif // _CAIRO_
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, RdrCreateConnection)
|
|
#pragma alloc_text(PAGE, RdrInvalidateServerConnections)
|
|
#pragma alloc_text(PAGE, RdrDisconnectConnection)
|
|
#pragma alloc_text(PAGE, RdrDeleteConnection)
|
|
#pragma alloc_text(PAGE, RdrGetConnectionReferences)
|
|
#pragma alloc_text(PAGE, GetFcbReferences)
|
|
#pragma alloc_text(PAGE, FindConnection)
|
|
#pragma alloc_text(PAGE, AllocateConnection)
|
|
#pragma alloc_text(PAGE, FindServerList)
|
|
#pragma alloc_text(PAGE, RdrForeachServer)
|
|
#pragma alloc_text(PAGE, AllocateConnectList)
|
|
#pragma alloc_text(PAGE, AllocateServerList)
|
|
#pragma alloc_text(PAGE, FreeServerList)
|
|
#pragma alloc_text(PAGE, FreeConnectList)
|
|
#pragma alloc_text(PAGE, CallServer)
|
|
#pragma alloc_text(PAGE, GetTimeZone)
|
|
#pragma alloc_text(PAGE, CreateTreeConnection)
|
|
#pragma alloc_text(PAGE, BuildLanmanConnect)
|
|
#pragma alloc_text(PAGE, BuildCoreConnect)
|
|
#pragma alloc_text(PAGE, BuildSessionSetupAndX)
|
|
#pragma alloc_text(PAGE, BuildTreeConnectAndX)
|
|
#pragma alloc_text(PAGE, TreeDisconnect)
|
|
#pragma alloc_text(PAGE, RdrEvaluateTimeouts)
|
|
#pragma alloc_text(PAGE, EvaluateServerTimeouts)
|
|
#pragma alloc_text(INIT, RdrpInitializeConnectPackage)
|
|
#pragma alloc_text(PAGE, DelayedDereferenceServer)
|
|
#pragma alloc_text(PAGE, RdrReconnectConnection)
|
|
#pragma alloc_text(PAGE, RdrReferenceConnection)
|
|
#pragma alloc_text(PAGE, RdrDereferenceConnection)
|
|
#pragma alloc_text(PAGE, RdrSetConnectlistFlag)
|
|
#pragma alloc_text(PAGE, RdrResetConnectlistFlag)
|
|
#pragma alloc_text(PAGE, RdrScanForDormantConnections)
|
|
#pragma alloc_text(PAGE, LogonSessionTerminationHandler)
|
|
#pragma alloc_text(PAGE, RdrHandleLogonSessionTermination)
|
|
|
|
|
|
#ifdef PAGING_OVER_THE_NET
|
|
#pragma alloc_text(PAGE, CountPagingFiles)
|
|
#endif
|
|
|
|
#pragma alloc_text(PAGE1CONN, RdrReferenceServer)
|
|
#pragma alloc_text(PAGE1CONN, RdrDereferenceServer)
|
|
#pragma alloc_text(PAGE1CONN, CleanupTransportConnection)
|
|
|
|
#pragma alloc_text(PAGE2VC, NegotiateCallback)
|
|
#pragma alloc_text(PAGE2VC, CoreTreeConnectCallback)
|
|
#pragma alloc_text(PAGE2VC, TreeConnectCallback)
|
|
#pragma alloc_text(PAGE2VC, TreeConnectComplete)
|
|
#pragma alloc_text(PAGE2VC, ProcessTreeConnectAndXBuffer)
|
|
|
|
#endif
|
|
//
|
|
// Public routines
|
|
//
|
|
|
|
NTSTATUS
|
|
RdrCreateConnection (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PUNICODE_STRING ServerName,
|
|
IN PUNICODE_STRING ShareName,
|
|
IN PTRANSPORT Transport OPTIONAL,
|
|
IN OUT PULONG Disposition,
|
|
OUT PCONNECTLISTENTRY *Connection,
|
|
IN ULONG Type
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function scans the global connectlist database to find if the
|
|
specified remote connection exists.
|
|
|
|
If an appropriate connection is found, it will increment the reference
|
|
count of the structure and return it.
|
|
|
|
If no connectlist that matches the input name is found, it will create
|
|
a new connection that refers to the remote resource
|
|
|
|
Arguments:
|
|
|
|
IN PUNICODE_STRING ServerName - Supplies the remote name to connect to
|
|
IN PUNICODE_STRING ShareName - Supplies the remote name to connect to
|
|
IN OUT PULONG Disposition - Disposition of connection
|
|
OUT PCONNECTLISTENTRY *Connection - Returns the connection if successful
|
|
IN ULONG Type - Supplies the type of the connection
|
|
|
|
The following describe the meaning of the disposition parameter for a
|
|
Connection:
|
|
|
|
FILE_CREATE:
|
|
If there is an existing connection to the server, return an error.
|
|
Otherwise, create a new connection.
|
|
|
|
FILE_OPEN:
|
|
If there is an existing connection to the server, open it.
|
|
Otherwise, return an error.
|
|
|
|
FILE_OPEN_IF:
|
|
If there is an existing connection to the server, open it.
|
|
Otherwise, create a new connection.
|
|
|
|
All other dispositions will return STATUS_INVALID_PARAMETER.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Final status of connection request.
|
|
|
|
--*/
|
|
|
|
{
|
|
#ifndef _IDWBUILD
|
|
ULONG i;
|
|
#endif
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(*Disposition != FILE_SUPERSEDE);
|
|
ASSERT(*Disposition != FILE_OVERWRITE);
|
|
ASSERT(*Disposition != FILE_OVERWRITE_IF);
|
|
|
|
//
|
|
// Initialize the connection to be returned.
|
|
//
|
|
|
|
*Connection = NULL;
|
|
|
|
|
|
if ((*Disposition==FILE_SUPERSEDE) || (*Disposition==FILE_OVERWRITE) ||
|
|
(*Disposition==FILE_OVERWRITE_IF)) {
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
#ifndef _IDWBUILD
|
|
for (i = 0;i < RdrNumberOfIllegalServerNames ; i++ ) {
|
|
UNICODE_STRING BadServerName, BadShareName;
|
|
|
|
RtlInitUnicodeString(&BadServerName, RdrIllegalServerNames[i].ServerName);
|
|
RtlInitUnicodeString(&BadShareName, RdrIllegalServerNames[i].ShareName);
|
|
|
|
if ((RtlEqualUnicodeString(ServerName, &BadServerName, TRUE) ) &&
|
|
(RtlEqualUnicodeString(ShareName, &BadShareName, TRUE))) {
|
|
|
|
DbgPrint("Access to \\\\%wZ\\%wZ has been disallowed on this build\n", ServerName, ShareName);
|
|
return(STATUS_ACCESS_DENIED);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// We are going to be modifying the connection database - claim the
|
|
// connection database mutex
|
|
//
|
|
|
|
if (!NT_SUCCESS(KeWaitForMutexObject(&RdrDatabaseMutex, // Object to wait.
|
|
Executive, // Reason for waiting
|
|
KernelMode, // Processor mode
|
|
FALSE, // Alertable
|
|
NULL))) {
|
|
InternalError(("Unable to claim connection mutex in GetConnection"));
|
|
}
|
|
|
|
//
|
|
// Scan to see if we have an existing connection that refers to
|
|
// this connection and if we do, return it.
|
|
//
|
|
|
|
Status = FindConnection(ServerName, ShareName, Transport, Connection, Type);
|
|
|
|
if (!NT_SUCCESS(Status) && ((*Disposition == FILE_OPEN) ||
|
|
(*Connection != NULL))) {
|
|
|
|
//
|
|
// We were unable to find an existing connection. Since the
|
|
// create disposition indicated that we had to find an existing
|
|
// connection, return the error.
|
|
//
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
return Status;
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
BOOLEAN GrabbedDormantConnection;
|
|
|
|
ASSERT ((*Connection) != NULL);
|
|
|
|
//
|
|
// If we are to either open or open_if the file, return now.
|
|
//
|
|
|
|
GrabbedDormantConnection = FALSE;
|
|
|
|
if ((*Connection)->Flags & CLE_DORMANT) {
|
|
|
|
//
|
|
// We allocated a dormant connection. We want to clear the
|
|
// dormant state on the connection before releasing the
|
|
// connection mutex.
|
|
//
|
|
|
|
GrabbedDormantConnection = TRUE;
|
|
|
|
(*Connection)->Flags &= ~CLE_DORMANT;
|
|
|
|
RdrNumDormantConnections -= 1;
|
|
}
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
if (*Disposition == FILE_OPEN_IF || *Disposition == FILE_OPEN) {
|
|
|
|
//
|
|
// We found an existing connection. Our disposition indicated
|
|
// that we were to open the file if we were able to find an
|
|
// existing connection, so return this connection.
|
|
//
|
|
|
|
*Disposition = FILE_OPENED;
|
|
return Status;
|
|
|
|
} else {
|
|
if (*Disposition == FILE_CREATE) {
|
|
//
|
|
// We found an existing connection. Our disposition indicated
|
|
// that we had to create a new connection, so we want to
|
|
// fail this request and return an error.
|
|
//
|
|
|
|
//
|
|
// But Wait!
|
|
//
|
|
// It's possible that we got a dormant connection from
|
|
// findconnection! In that case, we really CREATED
|
|
// a connection, and we want to return that connection
|
|
// successfully.
|
|
//
|
|
|
|
if (GrabbedDormantConnection) {
|
|
|
|
*Disposition = FILE_CREATED;
|
|
return Status;
|
|
|
|
} else {
|
|
|
|
//
|
|
// This wasn't a dormant connection we snarfed, so
|
|
// we need to return an error to the caller.
|
|
//
|
|
|
|
Status = STATUS_OBJECT_NAME_COLLISION;
|
|
|
|
RdrDereferenceConnection(Irp, *Connection, NULL, FALSE);
|
|
|
|
return Status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// We have to special case STATUS_BAD_DEVICE_TYPE.
|
|
//
|
|
// This error is returned if we want to look up a tree connection for a
|
|
// specific type, but we find an existing connection for another type.
|
|
//
|
|
// In this case, we want to return the error instead of creating a new
|
|
// connection.
|
|
//
|
|
|
|
if (Status == STATUS_BAD_DEVICE_TYPE) {
|
|
goto ReturnStatus; // Return error.
|
|
}
|
|
|
|
//
|
|
// We had to create this connection..
|
|
//
|
|
|
|
*Disposition = FILE_CREATED;
|
|
|
|
//
|
|
// We don't have a connection for this guy, this means we have to create
|
|
// a new connection.
|
|
//
|
|
|
|
Status = AllocateConnection (ServerName, ShareName, Transport, Connection);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
//
|
|
// Set the initial type of the newly allocated connection
|
|
//
|
|
|
|
(*Connection)->Type = Type;
|
|
|
|
}
|
|
//
|
|
// We're all done manipulating the connection database, release the mutex
|
|
//
|
|
|
|
ReturnStatus:
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
//
|
|
// We're done, return whatever status is appropriate
|
|
//
|
|
return Status;
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
RdrReconnectConnection (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will re-establish an invalid network connection.
|
|
|
|
|
|
Arguments:
|
|
|
|
IN PCONNECTLISTENTRY Cle - Supplies the connection to re-establish
|
|
IN PSECURITY_ENTRY Se - Security entry to associate with connection.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of reconnect operation.
|
|
|
|
|
|
Notes:
|
|
A couple of quick notes on how reconnection works are in order here.
|
|
|
|
Each ServerListEntry contains a mutex that is claimed when the SLE
|
|
is being connected. This prevents other threads in the system from coming
|
|
in while we are determining things like which sessions are valid in the
|
|
server, etc.
|
|
|
|
When we come in, we test to see if the connection has been disconnected.
|
|
We only test the connection bit, because the if the server has been
|
|
disconnected, then the connection is invalid, and if the connection is
|
|
valid, then the server MUST also be valid. (This is not entirely true,
|
|
since it is possible that the VC has just gone down, and the server
|
|
has been invalidated, but the cle has not yet been invalidated).
|
|
|
|
If the connection IS valid, then we return immediately. There is a
|
|
race condition between testing this bit and it getting turned off, but
|
|
there are only two cases that this can cause problems:
|
|
|
|
1) The connection is marked as being invalid, but it in fact has been
|
|
reconnected by someone else. In that case, we will claim the
|
|
SLE's reconnect mutex and wait until the SLE has been reconnected,
|
|
and then fall through both the cases where the SLE/CLE are not
|
|
disconnected. With appropriate work, this race can be closed,
|
|
but it is not necessary to do so.
|
|
|
|
2) The connection is marked as being valid, but has not yet been marked
|
|
as invalid (because of a VC dropping, etc). This race CANNOT be
|
|
closed, so we allow the caller a free reconnect if the request
|
|
fails (in RdrNetTranceiveWithCallback).
|
|
|
|
The other two cases (for completeness) are:
|
|
|
|
3) The connection is marked as being invalid, and is still invalid.
|
|
In this case, we simply go through the reconnect logic as normal.
|
|
|
|
4) The connection is marked as being valid, and is still valid.
|
|
In this case, we simply return success.
|
|
|
|
|
|
The reconnect logic is careful to not turn off the disconnect bit
|
|
in either the SLE, or the CLE before the connection has actually been
|
|
re-established, this avoids our having to wait for an event in the
|
|
successful case (where the connection is already connected).
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PSERVERLISTENTRY Server;
|
|
// PTRANSPORT_CONNECTION TransportConnection;
|
|
// PBOOLEAN ConnectionFlag;
|
|
|
|
PAGED_CODE();
|
|
|
|
dprintf(DPRT_CONNECT, ("RdrReconnectConnection \\%wZ\\%wZ\n", &Connection->Server->Text, &Connection->Text));
|
|
|
|
ASSERT (ARGUMENT_PRESENT(Se));
|
|
|
|
Server = Connection->Server;
|
|
|
|
// if (Se->Flags & SE_USE_SPECIAL_IPC) {
|
|
// ConnectionFlag = &Connection->HasSpecialTreeId;
|
|
// } else {
|
|
// ConnectionFlag = &Connection->HasTreeId;
|
|
// }
|
|
//
|
|
// TransportConnection = Se->TransportConnection;
|
|
|
|
//
|
|
// If all three conditions (Valid VC, valid Tree Id, and Valid Se)
|
|
// are met, just wait until the VC has been completely established
|
|
// and return.
|
|
//
|
|
|
|
if ((Server->ConnectionValid) &&
|
|
(Connection->HasTreeId) &&
|
|
(Se->Flags & SE_HAS_SESSION)) {
|
|
|
|
ASSERT (KeGetCurrentIrql() <= APC_LEVEL);
|
|
|
|
return (Status = STATUS_SUCCESS);
|
|
}
|
|
|
|
//
|
|
// Acquire the lock synchronizing reconnect and disconnect.
|
|
//
|
|
// N.B. We have to acquire the RawResource first in order to avoid
|
|
// deadlock (see Bug 31262) RdrFsdCreate calls this
|
|
// routine with this lock already owned, but other callers
|
|
// do not. See comments in RdrDisconnectConnection.
|
|
//
|
|
|
|
ExAcquireResourceShared(&Server->RawResource, TRUE );
|
|
|
|
ExAcquireResourceExclusive(&Server->SessionStateModifiedLock, TRUE);
|
|
|
|
try {
|
|
|
|
//
|
|
// Now that we have the connection state locked, retry the check - it
|
|
// might have gotten better by the time we got the SSM lock exclusive.
|
|
//
|
|
|
|
if ((Server->ConnectionValid) &&
|
|
(Connection->HasTreeId) &&
|
|
(Se->Flags & SE_HAS_SESSION)) {
|
|
|
|
ASSERT (KeGetCurrentIrql() <= APC_LEVEL);
|
|
|
|
try_return(Status = STATUS_SUCCESS);
|
|
}
|
|
|
|
//
|
|
// If the server in question is disconnected, call the server.
|
|
//
|
|
|
|
//
|
|
// Make sure that the connection is idle before we start
|
|
// munging with the VC.
|
|
//
|
|
|
|
if (!Server->ConnectionValid) {
|
|
|
|
ACQUIRE_REQUEST_RESOURCE_EXCLUSIVE( Server, TRUE, 1 );
|
|
|
|
//
|
|
// Retest and see if anyone else somehow managed to make it
|
|
// better.
|
|
//
|
|
|
|
if (!Server->ConnectionValid) {
|
|
|
|
//
|
|
// We do not have an existing VC with the server.
|
|
//
|
|
// We now want to establish the connection to the remote server.
|
|
// However, if we recently failed to connect to the server, let's
|
|
// not bother trying again.
|
|
//
|
|
|
|
if ( !NT_SUCCESS(Server->LastConnectStatus) &&
|
|
((Server->LastConnectTime+FAILED_CONNECT_TIMEOUT) > RdrCurrentTime) ) {
|
|
Status = Server->LastConnectStatus;
|
|
} else {
|
|
Status = CallServer(Irp, Connection, Se);
|
|
}
|
|
}
|
|
|
|
RELEASE_REQUEST_RESOURCE( Server, 2 );
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ASSERT (KeGetCurrentIrql() <= APC_LEVEL);
|
|
try_return(Status);
|
|
}
|
|
|
|
|
|
//
|
|
// On MSNET servers we support connection to the server. There is no
|
|
// connection to IPC$ and the only operation available on the handle
|
|
// is to enumerate print jobs on the one and only print queue. Since
|
|
// we have already determined its an msnet server we can return success.
|
|
//
|
|
|
|
if (((Server->Capabilities & DF_LANMAN10 ) == 0) &&
|
|
(Connection->Type == CONNECT_IPC)) {
|
|
|
|
Connection->HasTreeId = FALSE;
|
|
|
|
// Connection->HasSpecialTreeId = FALSE;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
ASSERT (KeGetCurrentIrql() <= APC_LEVEL);
|
|
|
|
try_return(Status);
|
|
}
|
|
|
|
ASSERT (KeGetCurrentIrql() <= APC_LEVEL);
|
|
|
|
//
|
|
// Reclaim the connection valid lock to check the CLE to see if
|
|
// it's invalid.
|
|
//
|
|
|
|
//
|
|
// Now establish the tree connection and logon session.
|
|
//
|
|
|
|
if (!(Connection->HasTreeId) || !(Se->Flags & SE_HAS_SESSION)) {
|
|
|
|
ASSERT (Se->PotentialNext.Flink != NULL);
|
|
|
|
ASSERT (Se->PotentialNext.Blink != NULL);
|
|
|
|
//
|
|
// We now have a virtual circuit to the remote server.
|
|
//
|
|
// Depending on the level of protocol negotiated (and a bunch of
|
|
// other things), we want to build and exchange a
|
|
// SessionSetup&TreeConnect SMB with the remote server.
|
|
//
|
|
|
|
#ifdef _CAIRO_
|
|
if ( (Server->Capabilities & DF_KERBEROS)
|
|
&&
|
|
!(Se->Flags & (SE_IS_NULL_SESSION | SE_HAS_SESSION)) ) {
|
|
|
|
//
|
|
// We don't have a session, but the server claims
|
|
// to support Kerberors. So we will try connecting
|
|
// the Kerberos way.
|
|
//
|
|
|
|
dprintf(DPRT_CONNECT, ("Setting up Cairo session\n"));
|
|
|
|
Status = SetupCairoSession(Irp,Connection,Se);
|
|
|
|
if ((!Connection->HasTreeId && NT_SUCCESS(Status))
|
|
||
|
|
(((Status == STATUS_NO_LOGON_SERVERS) ||
|
|
(Status == STATUS_NO_SUCH_LOGON_SESSION)) &&
|
|
RdrCleanSecurityContexts(Se)) )
|
|
{
|
|
|
|
// ASSERT((Se->Flags & SE_HAS_SESSION) == 0);
|
|
|
|
dprintf(DPRT_CONNECT, ("Session established, getting Tree connect\n"));
|
|
|
|
Status = CreateTreeConnection(Irp, Connection, Connection->Type, Se);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Must be downlevel connection
|
|
//
|
|
|
|
dprintf(DPRT_CONNECT, ("Setting up a downlevel session and tree connect\n"));
|
|
|
|
Status = CreateTreeConnection(Irp, Connection, Connection->Type, Se);
|
|
|
|
}
|
|
#else // _CAIRO_
|
|
Status = CreateTreeConnection(Irp, Connection, Connection->Type, Se);
|
|
#endif // _CAIRO_
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
SeMarkLogonSessionForTerminationNotification(
|
|
&Se->LogonId);
|
|
|
|
} else {
|
|
|
|
ULONG NumberOfValidConnections = 0;
|
|
PLIST_ENTRY ConnectEntry;
|
|
|
|
if (Status == STATUS_USER_SESSION_DELETED) {
|
|
RdrInvalidateConnectionActiveSecurityEntries(NULL,
|
|
Server,
|
|
Connection,
|
|
FALSE,
|
|
Se->UserId
|
|
);
|
|
|
|
|
|
ASSERT (!FlagOn(Se->Flags, SE_HAS_SESSION));
|
|
|
|
Status = CreateTreeConnection(Irp, Connection, Connection->Type, Se);
|
|
|
|
//
|
|
// If it got better after invalidating the security
|
|
// entry, we can return the successful status.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
SeMarkLogonSessionForTerminationNotification(
|
|
&Se->LogonId);
|
|
try_return(Status);
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef _CAIRO_
|
|
else if ((Status == STATUS_LOGON_FAILURE) ||
|
|
(Status == STATUS_ACCESS_DENIED)) {
|
|
|
|
if (FlagOn(Se->Flags, SE_RETURN_ON_ERROR)) {
|
|
|
|
Status = CreateTreeConnection(Irp, Connection, Connection->Type, Se);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
SeMarkLogonSessionForTerminationNotification(
|
|
&Se->LogonId);
|
|
try_return(Status);
|
|
}
|
|
|
|
}
|
|
}
|
|
#endif // _CAIRO_
|
|
|
|
//
|
|
// If the attempt to establish the connection failed, then
|
|
// see if we have any other valid connections on the server
|
|
// and drop the VC if we don't have any more.
|
|
//
|
|
|
|
KeWaitForMutexObject(&RdrDatabaseMutex, Executive,
|
|
KernelMode, FALSE, NULL);
|
|
|
|
|
|
for (ConnectEntry = Server->CLEHead.Flink ;
|
|
ConnectEntry != &Server->CLEHead ;
|
|
ConnectEntry = ConnectEntry->Flink) {
|
|
|
|
PCONNECTLISTENTRY ConnectTemp = CONTAINING_RECORD(ConnectEntry,
|
|
CONNECTLISTENTRY,
|
|
SiblingNext);
|
|
|
|
if (ConnectTemp->HasTreeId) {
|
|
NumberOfValidConnections += 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
//
|
|
// If there are no longer any valid connections to this server
|
|
// on this VC, "cleanup" the connection (logoff users and drop
|
|
// VC).
|
|
//
|
|
|
|
if (NumberOfValidConnections == 0) {
|
|
CleanupTransportConnection(Irp, Connection, Server);
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// The connection is valid, we can now return.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
try_exit:NOTHING;
|
|
} finally {
|
|
|
|
ASSERT (KeGetCurrentIrql() <= APC_LEVEL);
|
|
|
|
ExReleaseResource(&Server->SessionStateModifiedLock);
|
|
ExReleaseResource(&Server->RawResource);
|
|
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
RdrDereferenceConnection (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se OPTIONAL,
|
|
IN BOOLEAN ForcablyDeleteConnection
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will decrement the ConnectList reference count, and
|
|
if it goes to zero, it will mark the ConnectList as dormant.
|
|
|
|
Arguments:
|
|
|
|
IN PIRP Irp = Supplies an IRP to use during the dereference.
|
|
IN PCONNECTLISTENTRY Connection - Supplies the connection to dereference
|
|
IN PSECURITY_ENTRY Se - Supplies an optional security entry to use for tree
|
|
disconnect if necessary.
|
|
IN BOOLEAN ForcablyDeleteConnection - if TRUE, don't allow connection to
|
|
be marked as dormant.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
PAGED_CODE();
|
|
|
|
dprintf(DPRT_CONNECT, ("RdrDereferenceConnection\n"));
|
|
// DbgBreakPoint();
|
|
|
|
//
|
|
// Claim the connectlist database mutex.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status = KeWaitForMutexObject(&RdrDatabaseMutex, // Object
|
|
Executive, // Reason for waiting
|
|
KernelMode, // Processor mode
|
|
FALSE, // Alertable
|
|
NULL))) {
|
|
InternalError(("Unable to claim connection mutex in CloseConnectRef"));
|
|
return FALSE;
|
|
}
|
|
|
|
if (Connection->Flags & CLE_DORMANT || Connection->RefCount ) {
|
|
|
|
if ((Connection->Flags & CLE_DORMANT) ||
|
|
(--Connection->RefCount == 0)) {
|
|
|
|
ASSERT(IsListEmpty(&Connection->FcbChain));
|
|
|
|
//
|
|
// If the caller won't let this connection go dormant,
|
|
// or the connection doesn't have a valid tree id,
|
|
// don't let the connection go dormant, blow it away.
|
|
//
|
|
|
|
if (ForcablyDeleteConnection ||
|
|
(!Connection->HasTreeId)) {
|
|
|
|
dprintf(DPRT_CONNECT, ("Reference count to connection on \\%wZ\\%wZ destroying connection\n", &Connection->Server->Text, &Connection->Text));
|
|
|
|
//
|
|
// The Connection reference count just went to 0 remove it.
|
|
//
|
|
|
|
RemoveEntryList(&Connection->GlobalNext);
|
|
|
|
//
|
|
// Next we want to remove the ConnectList from the per
|
|
// ServerList ConnectList chain.
|
|
|
|
RemoveEntryList(&Connection->SiblingNext);
|
|
|
|
//
|
|
// If this connection was dormant, then decrement the
|
|
// number of dormant connections.
|
|
//
|
|
|
|
if (Connection->Flags & CLE_DORMANT) {
|
|
|
|
RdrNumDormantConnections -- ;
|
|
}
|
|
|
|
//
|
|
// Now that we are done modifying the connection database, we
|
|
// can release the connection database mutex.
|
|
//
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
RdrDisconnectConnection(Irp, Connection, FALSE, Se);
|
|
|
|
RdrDereferenceServer(Irp, Connection->Server);
|
|
|
|
//
|
|
// If this connection has any default security entries
|
|
// associated with it, remove the reference to them.
|
|
//
|
|
|
|
ExAcquireResourceExclusive(&RdrDefaultSeLock, TRUE);
|
|
|
|
while (!IsListEmpty(&Connection->DefaultSeList)) {
|
|
PLIST_ENTRY SeEntry;
|
|
PSECURITY_ENTRY Se2;
|
|
|
|
SeEntry = RemoveHeadList(&Connection->DefaultSeList);
|
|
|
|
Se2 = CONTAINING_RECORD(SeEntry, SECURITY_ENTRY, DefaultSeNext);
|
|
//RdrLog(( "c rmv c", NULL, 12, Se2, Connection, 0, Se2->Connection, Se2->Server, Connection->Server,
|
|
// Connection->Server->DefaultSeList.Flink, Connection->Server->DefaultSeList.Blink,
|
|
// Connection->DefaultSeList.Flink, Connection->DefaultSeList.Blink,
|
|
// Se2->DefaultSeNext.Flink, Se2->DefaultSeNext.Blink ));
|
|
|
|
Se2->DefaultSeNext.Flink = NULL;
|
|
|
|
Se2->DefaultSeNext.Blink = NULL;
|
|
|
|
ExReleaseResource(&RdrDefaultSeLock);
|
|
|
|
//
|
|
// This security entry had better be inactive by now.
|
|
//
|
|
|
|
ASSERT (Se2->ActiveNext.Flink == NULL);
|
|
|
|
ASSERT (Se2->ActiveNext.Blink == NULL);
|
|
|
|
RdrDereferenceSecurityEntry(Se2->NonPagedSecurityEntry);
|
|
|
|
ExAcquireResourceExclusive(&RdrDefaultSeLock, TRUE);
|
|
|
|
}
|
|
|
|
ExReleaseResource(&RdrDefaultSeLock);
|
|
|
|
FreeConnectList(Connection);
|
|
|
|
return TRUE;
|
|
} else {
|
|
|
|
//
|
|
// Mark this connection as a dormant connection and kick
|
|
// the dormant connection scavenger thread to get rid of
|
|
// the connection.
|
|
//
|
|
|
|
Connection->Flags |= CLE_DORMANT;
|
|
|
|
RdrNumDormantConnections ++ ;
|
|
|
|
ASSERT(IsListEmpty(&Connection->FcbChain));
|
|
|
|
dprintf(DPRT_CONNECT, ("Reference count to connection on \\%wZ\\%wZ going to %lx. Going dormant.\n", &Connection->Server->Text, &Connection->Text, Connection->RefCount));
|
|
|
|
Connection->DormantTimeout =
|
|
RdrCurrentTime + RdrData.DormantConnectionTimeout;
|
|
|
|
//
|
|
// Note: Since we are not deleting the connection,
|
|
// we do not remove the final reference to the Se
|
|
// associated with this connection. We do that when
|
|
// we finally delete the session, even though there
|
|
// are no open files on that session.
|
|
//
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
//
|
|
// We did not delete the connection, so indicate that.
|
|
//
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dprintf(DPRT_CONNECT, ("Decrement reference on \\%wZ\\%wZ, now at %lx\n", &Connection->Server->Text, &Connection->Text, Connection->RefCount));
|
|
#if RDRDBG
|
|
{
|
|
LONG RefCount;
|
|
LONG ChainLength;
|
|
|
|
ChainLength = NumEntriesList(&Connection->FcbChain);
|
|
RefCount = Connection->RefCount;
|
|
if (ChainLength > RefCount) {
|
|
dprintf(DPRT_CONNECT, ("Conn %lx, Fcb: %lx Ref: %lx\n",
|
|
Connection,
|
|
NumEntriesList(&Connection->FcbChain),
|
|
Connection->RefCount));
|
|
ASSERT(ChainLength <= Connection->RefCount);
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
} else {
|
|
|
|
InternalError(("RdrDereferenceConnection: Decrement reference through zero"));
|
|
RdrInternalError(EVENT_RDR_CONNECTION_REFERENCE);
|
|
|
|
}
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
RdrReferenceConnection (
|
|
IN PCONNECTLISTENTRY Connection
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates a new reference to an existing connection.
|
|
|
|
Arguments:
|
|
|
|
|
|
IN PCONNECTLISTENTRY Connection - Connection to bump reference on.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Claim the connectlist database mutex.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status = KeWaitForMutexObject(&RdrDatabaseMutex, // Object
|
|
Executive, // Reason for waiting
|
|
KernelMode, // Processor mode
|
|
FALSE, // Alertable
|
|
NULL))) {
|
|
InternalError(("Unable to claim connection mutex in CloseConnectRef"));
|
|
return Status;
|
|
}
|
|
|
|
|
|
Connection->RefCount += 1; // Indicate new reference
|
|
|
|
|
|
//
|
|
// If this connection was dormant, it is no longer dormant.
|
|
//
|
|
|
|
if (Connection->Flags & CLE_DORMANT) {
|
|
Connection->Flags &= ~CLE_DORMANT;
|
|
|
|
ASSERT (RdrNumDormantConnections > 0);
|
|
|
|
RdrNumDormantConnections -= 1;
|
|
}
|
|
|
|
dprintf(DPRT_CONNECT, ("New reference on connection \\%wZ\\%wZ, now %lx\n", &Connection->Server->Text, &Connection->Text, Connection->RefCount));
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID
|
|
RdrScanForDormantConnections (
|
|
IN ULONG NumberOfConnectionsToFree,
|
|
IN PTRANSPORT Transport OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine scans the connection database for dormant connections that
|
|
have expired. When it detects one, it forcably removes the reference
|
|
to the connection.
|
|
|
|
|
|
Arguments:
|
|
|
|
ULONG NumberOfConnectionsToFree - Indicates the number of dormant
|
|
connections that must be freed.
|
|
|
|
PTRANSPORT Transport OPTIONAL - If specified, specifies the transport
|
|
the connection must be on to free it.
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG CurrentTime;
|
|
PLIST_ENTRY ConnectionEntry;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// If there are no dormant connections, then don't do anything, just
|
|
// return.
|
|
//
|
|
// Please note that we do this check outside the mutex. This is ok,
|
|
// since the worst thing that can happen is that we will miss a chance
|
|
// to scan the database.
|
|
//
|
|
|
|
if (RdrNumDormantConnections == 0) {
|
|
return;
|
|
}
|
|
|
|
dprintf(DPRT_CONNECT, ("RdrScanForDormantConnections.."));
|
|
|
|
//
|
|
// First acquire the connection package exclusion mutex.
|
|
//
|
|
// If we are unable to acquire the mutex, bail out immediately.
|
|
//
|
|
|
|
if (!NT_SUCCESS(KeWaitForMutexObject(&RdrDatabaseMutex,
|
|
Executive, KernelMode, FALSE, NULL))) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Snapshot the current redir time onto the stack.
|
|
//
|
|
|
|
CurrentTime = RdrCurrentTime;
|
|
|
|
for (ConnectionEntry = RdrConnectHead.Flink ;
|
|
ConnectionEntry != &RdrConnectHead ;
|
|
ConnectionEntry = ConnectionEntry->Flink) {
|
|
PCONNECTLISTENTRY Connection = CONTAINING_RECORD(ConnectionEntry, CONNECTLISTENTRY,
|
|
GlobalNext);
|
|
|
|
Connection->Flags &= ~CLE_SCANNED;
|
|
|
|
}
|
|
|
|
RestartScan:
|
|
for (ConnectionEntry = RdrConnectHead.Flink ;
|
|
ConnectionEntry != &RdrConnectHead ;
|
|
ConnectionEntry = ConnectionEntry->Flink) {
|
|
PCONNECTLISTENTRY Connection;
|
|
|
|
Connection = CONTAINING_RECORD(ConnectionEntry, CONNECTLISTENTRY,
|
|
GlobalNext);
|
|
|
|
ASSERT(Connection->Signature == STRUCTURE_SIGNATURE_CONNECTLISTENTRY);
|
|
|
|
if (!FlagOn(Connection->Flags, CLE_SCANNED) &&
|
|
FlagOn(Connection->Flags, CLE_DORMANT)) {
|
|
|
|
//
|
|
// Mark that this connection has been seen during this dormant
|
|
// scan.
|
|
//
|
|
|
|
Connection->Flags |= CLE_SCANNED;
|
|
|
|
//
|
|
// This connection is dormant, check to see if it's expired.
|
|
//
|
|
|
|
dprintf(DPRT_SCAVTHRD,("Found dormant connection %lx",Connection));
|
|
|
|
if ((NumberOfConnectionsToFree != 0) ||
|
|
(CurrentTime > Connection->DormantTimeout)) {
|
|
PSERVERLISTENTRY Server = Connection->Server;
|
|
BOOLEAN ForceDisconnection = TRUE;
|
|
BOOLEAN ConnectionRawAcquired = FALSE;
|
|
BOOLEAN SessionStateModifiedAcquired = FALSE;
|
|
BOOLEAN ConnectionOutstandingRequestsAcquired = FALSE;
|
|
|
|
dprintf(DPRT_SCAVTHRD, ("Removing dormant connection to \\%wZ\\%wZ", &Connection->Server->Text, &Connection->Text));
|
|
|
|
dprintf(DPRT_SCAVTHRD, ("Current Time: %lx Timeout: %lx\n", CurrentTime, Connection->DormantTimeout)) ;
|
|
|
|
if (ARGUMENT_PRESENT(Transport) &&
|
|
(CurrentTime <= Connection->DormantTimeout)) {
|
|
|
|
//
|
|
// If we are coming through this path because we need to
|
|
// free up a connection on a specific transport, make
|
|
// sure that this connection is on a specific transport.
|
|
//
|
|
// If not, try the next connection.
|
|
//
|
|
|
|
if ((Connection->Server->ConnectionContext == NULL) ||
|
|
(Connection->Server->ConnectionContext->TransportProvider == NULL) ||
|
|
(Connection->Server->ConnectionContext->TransportProvider->PagedTransport != Transport)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Release the connection mutex before dereferencing the
|
|
// connection - We might have to release a file object
|
|
// which would cause a mutex level violation.
|
|
//
|
|
|
|
RdrReferenceConnection(Connection);
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
RdrReferenceServer(Server);
|
|
|
|
if (ExAcquireResourceShared(&Server->RawResource, FALSE)) {
|
|
ConnectionRawAcquired = TRUE;
|
|
} else {
|
|
dprintf(DPRT_SCAVTHRD, ("Raw I/O outstanding, bailing out\n")) ;
|
|
|
|
ForceDisconnection = FALSE;
|
|
}
|
|
|
|
if (ForceDisconnection &&
|
|
ExAcquireResourceExclusive(&Server->SessionStateModifiedLock, FALSE)) {
|
|
SessionStateModifiedAcquired = TRUE;
|
|
} else {
|
|
dprintf(DPRT_SCAVTHRD, ("Reconnect outstanding, bailing out\n")) ;
|
|
ForceDisconnection = FALSE;
|
|
}
|
|
|
|
if (ForceDisconnection &&
|
|
ACQUIRE_REQUEST_RESOURCE_EXCLUSIVE( Server, FALSE, 3 )) {
|
|
ConnectionOutstandingRequestsAcquired = TRUE;
|
|
} else {
|
|
dprintf(DPRT_SCAVTHRD, ("I/O outstanding, bailing out\n")) ;
|
|
ForceDisconnection = FALSE;
|
|
}
|
|
|
|
//
|
|
// We're about to free a dormant connection.
|
|
//
|
|
|
|
if (ForceDisconnection &&
|
|
NumberOfConnectionsToFree != 0) {
|
|
NumberOfConnectionsToFree -= 1;
|
|
}
|
|
|
|
//
|
|
// It's history, blow this puppy away..
|
|
//
|
|
|
|
RdrDereferenceConnection(NULL, Connection, NULL, ForceDisconnection);
|
|
|
|
#if DBG
|
|
Connection = NULL;
|
|
#endif
|
|
|
|
if (ConnectionOutstandingRequestsAcquired) {
|
|
RELEASE_REQUEST_RESOURCE( Server, 4 );
|
|
}
|
|
|
|
if (SessionStateModifiedAcquired) {
|
|
ExReleaseResource(&Server->SessionStateModifiedLock);
|
|
}
|
|
|
|
if (ConnectionRawAcquired) {
|
|
ExReleaseResource(&Server->RawResource);
|
|
}
|
|
|
|
RdrDereferenceServer(NULL, Server);
|
|
|
|
#if DBG
|
|
Server = NULL;
|
|
#endif
|
|
|
|
//
|
|
// If there are no longer any dormant connections, break
|
|
// out of the loop.
|
|
//
|
|
|
|
if (RdrNumDormantConnections == 0) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// First acquire the connection package exclusion mutex.
|
|
//
|
|
// If we are unable to acquire the mutex, bail out immediately.
|
|
//
|
|
|
|
if (!NT_SUCCESS(KeWaitForMutexObject(&RdrDatabaseMutex,
|
|
Executive, KernelMode, FALSE, NULL))) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// There are still dormant connections outstanding.
|
|
//
|
|
// Since we've just messed around with the list,
|
|
// we have to restart the scan at the start of the
|
|
// list.
|
|
//
|
|
|
|
goto RestartScan;
|
|
}
|
|
}
|
|
}
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
dprintf(DPRT_CONNECT, ("..Done\n"));
|
|
|
|
}
|
|
|
|
|
|
VOID
|
|
RdrInvalidateServerConnections(
|
|
IN PSERVERLISTENTRY Server
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called when a server disconnects from the workstation.
|
|
|
|
It will invalidate all the connectlistentries associated with this
|
|
server.
|
|
|
|
|
|
Arguments:
|
|
|
|
IN PSERVERLISTENTRY Server
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PLIST_ENTRY ConnectEntry, NextConnection;
|
|
PCONNECTLISTENTRY Connection;
|
|
|
|
PAGED_CODE();
|
|
|
|
dprintf(DPRT_ERROR, ("RdrInvalidateServerConnections.."));
|
|
|
|
//
|
|
// First acquire the connection package exclusion mutex.
|
|
//
|
|
// If we are unable to acquire the mutex, bail out immediately.
|
|
//
|
|
|
|
if (!NT_SUCCESS(KeWaitForMutexObject(&RdrDatabaseMutex,
|
|
Executive, KernelMode, FALSE, NULL))) {
|
|
return;
|
|
}
|
|
|
|
RdrReferenceDiscardableCode(RdrFileDiscardableSection);
|
|
|
|
//
|
|
// Scan the list of connection and flag the connection as invalid.
|
|
//
|
|
|
|
for (ConnectEntry = Server->CLEHead.Flink ;
|
|
ConnectEntry != &Server->CLEHead ;
|
|
ConnectEntry = NextConnection) {
|
|
|
|
Connection = CONTAINING_RECORD(ConnectEntry, CONNECTLISTENTRY, SiblingNext);
|
|
|
|
ASSERT (Connection->Signature == STRUCTURE_SIGNATURE_CONNECTLISTENTRY);
|
|
|
|
dprintf(DPRT_ERROR, ("Invalidating files on connection \\%wZ\\%wZ\n", &Server->Text, &Connection->Text));
|
|
|
|
Status = RdrReferenceConnection(Connection);
|
|
|
|
//
|
|
// Release the connection package mutex.
|
|
//
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
dprintf(DPRT_ERROR, ("Invalidate open files on \\%wZ\\%wZ\n", &Server->Text, &Connection->Text));
|
|
|
|
//
|
|
// Invalidate all the open files on this connection.
|
|
//
|
|
|
|
RdrInvalidateConnectionFiles(NULL, Connection, NULL, NULL, FALSE);
|
|
|
|
if (!NT_SUCCESS(KeWaitForMutexObject(&RdrDatabaseMutex,
|
|
Executive, KernelMode, FALSE, NULL))) {
|
|
InternalError(("Unable to acquire Connect MUTEX!!\n"));
|
|
}
|
|
|
|
NextConnection = ConnectEntry->Flink;
|
|
|
|
RdrDereferenceConnection(NULL, Connection, NULL, FALSE);
|
|
|
|
}
|
|
|
|
//
|
|
// Release the conection package mutext to allow requests to continue.
|
|
//
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
RdrDereferenceDiscardableCode(RdrFileDiscardableSection);
|
|
|
|
dprintf(DPRT_ERROR, ("..Done\n"));
|
|
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
RdrDisconnectConnection (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN BOOLEAN DeletingConnection,
|
|
IN PSECURITY_ENTRY Se OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will decrement the ConnectList reference count, and
|
|
if it goes to zero, it will mark the ConnectList as dormant.
|
|
|
|
Arguments:
|
|
|
|
IN PIRP Irp - Supplies an IRP to use for SMB exchanges.
|
|
IN PCONNECTLISTENTRY Connection - Supplies the connection to remove
|
|
IN BOOLEAN DeletingConnection - if TRUE remove default security entry
|
|
for user. This is set when we are
|
|
deleting the connection (RdrDeleteConnection).
|
|
IN PSECURITY_ENTRY Se - Supplies an optional security entry to dereference
|
|
If No Se is supplied, it means we should
|
|
delete BOTH transport connections on this
|
|
server.
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of operation
|
|
|
|
--*/
|
|
|
|
|
|
{
|
|
BOOLEAN ConnectMutexOwned = FALSE;
|
|
NTSTATUS Status;
|
|
ULONG NumberOfValidConnections;
|
|
PLIST_ENTRY ConnectEntry;
|
|
BOOLEAN SpecialIpcRawAcquired = FALSE;
|
|
BOOLEAN ConnectionRawAcquired = FALSE;
|
|
BOOLEAN SpecialIpcOutstandingAcquired = FALSE;
|
|
BOOLEAN ConnectionOutstandingAcquired = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
|
|
//
|
|
// Early out if we don't have any disconnecting to do.
|
|
//
|
|
|
|
if (!Connection->HasTreeId) {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Acquire the RAW I/O resource on this server.
|
|
//
|
|
//
|
|
// We need to do this because of the order of the synchronization
|
|
// hierarchy in the redirector.
|
|
//
|
|
// If we were to send a tree disconnect, then we would acquire the raw
|
|
// resource for shared access, and the hierarchy is:
|
|
//
|
|
// Server->RawResource
|
|
// Server->CreationLock
|
|
// Fcb->Lock
|
|
// Server->RawResource (SHARED ONLY, or exclusive w/o waiting)
|
|
// Server->SessionStateModifiedLock
|
|
//
|
|
// During raw I/O, these are acquired in the following order:
|
|
//
|
|
// Fcb->Lock
|
|
// Server->RawResource (Exclusive)
|
|
// Server->RawResource (Shared)
|
|
// Server->OutstandingRequestResource (Shared)
|
|
//
|
|
// This may look like an inversion of the synchronization hierarchy,
|
|
// but it's not because when we acquire the raw resource, if we can't
|
|
// acquire it immediately, we fall back to core (and acquire it for
|
|
// shared access).
|
|
//
|
|
// During dormant session disconnection, since we acquire raw for shared
|
|
// access, we need to make it:
|
|
//
|
|
// If there is a blocking pipe write (or read) outstanding to this
|
|
// server, and we come in to scan for dormant connections, we need to
|
|
// guarantee that this call will never block while waiting on the
|
|
// OutstandingRequestResource (which will happen at this time). To
|
|
// fix this, we acquire all 3 of the servers resources in
|
|
// RdrScanForDormantConnections and only call RdrDereferenceConnection
|
|
// if all 3 can be acquired (ie if there is absolutely no activity on
|
|
// the server.
|
|
//
|
|
// Server->RawResource (Shared, NoWait)
|
|
// Server->SessionStateModifiedLock (Exclusive, NoWait)
|
|
// Server->OutstandingRequestResource (Exclusive, NoWait)
|
|
//
|
|
// Server->RawResource (Shared)
|
|
// Server->SessionStateModifiedLock
|
|
// Server->OutstandingRequestResource (Exclusive)
|
|
// Server->OutstandingRequestResource (Shared)
|
|
//
|
|
// And during NetUseDel, we need to make it:
|
|
//
|
|
// Server->RawResource (Shared)
|
|
// Server->CreationLock (Exclusive)
|
|
// Server->RawResource (Shared)
|
|
// Server->SessionStateModifiedLock
|
|
// Server->OutstandingRequestResource (Exclusive)
|
|
// Server->RawResource (Shared)
|
|
// Server->OutstandingRequestResource (Shared)
|
|
//
|
|
// During opens, we acquire it in the following order (See the comment in
|
|
// create.c to see why we acquire raw before creation lock:
|
|
//
|
|
// Server->RawResource (Shared)
|
|
// Server->CreationLock (Shared)
|
|
// Server->SessionStateModifiedLock (Exclusive) (OPTIONAL)
|
|
// Server->OutstandingRequestResource (Exclusive) (OPTIONAL)
|
|
// Server->RawResource (Shared)
|
|
// Server->OutstandingRequestResource (Shared)
|
|
//
|
|
// During reconnect, we acquire it in the following order to guarantee
|
|
// that we don't reconnect while there are any outstanding requests:
|
|
//
|
|
// Server->RawResource (Shared)
|
|
// Server->SessionStateModifiedLock (Exclusive) (OPTIONAL)
|
|
// Server->OutstandingRequestResource (Exclusive) (OPTIONAL)
|
|
// Server->RawResource (Shared)
|
|
// Server->OutstandingRequestResource (Shared)
|
|
//
|
|
|
|
ExAcquireResourceShared(&Connection->Server->RawResource, TRUE );
|
|
|
|
ExAcquireResourceExclusive(&Connection->Server->SessionStateModifiedLock, TRUE);
|
|
|
|
try {
|
|
|
|
dprintf(DPRT_CONNECT, ("RdrDisconnectConnection. Disconnecting from connection %lx\n", Connection));
|
|
|
|
//
|
|
// Disconnect from this tree connection.
|
|
//
|
|
|
|
if (Connection->HasTreeId) {
|
|
|
|
Connection->HasTreeId = FALSE;
|
|
|
|
dprintf(DPRT_CONNECT, ("RdrDisconnectConnection: Tree disconnecting on %lx\n", Connection));
|
|
|
|
TreeDisconnect(Irp, Connection, Se, Connection->Server);
|
|
|
|
}
|
|
|
|
ASSERT (KeGetCurrentIrql() <= APC_LEVEL);
|
|
|
|
if (DeletingConnection) {
|
|
|
|
if (Se == NULL) {
|
|
RdrLogoffAllDefaultSecurityEntry(Irp, Connection, Connection->Server);
|
|
} else {
|
|
RdrLogoffDefaultSecurityEntry(Irp, Connection, Connection->Server, &Se->LogonId);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Acquire the connection mutex because we will be checking
|
|
// the reference counts for the connection now.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status = KeWaitForMutexObject(&RdrDatabaseMutex,
|
|
Executive, KernelMode, FALSE, NULL))) {
|
|
try_return(Status);
|
|
}
|
|
|
|
ConnectMutexOwned = TRUE;
|
|
|
|
//
|
|
// Now we walk the list of connections associated with this
|
|
// server and count up the number of valid connections associated
|
|
// with the server.
|
|
//
|
|
|
|
NumberOfValidConnections = 0;
|
|
|
|
for (ConnectEntry = Connection->Server->CLEHead.Flink ;
|
|
ConnectEntry != &Connection->Server->CLEHead ;
|
|
ConnectEntry = ConnectEntry->Flink) {
|
|
|
|
PCONNECTLISTENTRY Cle = CONTAINING_RECORD(ConnectEntry, CONNECTLISTENTRY, SiblingNext);
|
|
|
|
//
|
|
// If we are deleting both connections, check to see if any
|
|
// connections are outstanding on this connection.
|
|
//
|
|
|
|
if (Cle->HasTreeId) {
|
|
NumberOfValidConnections += 1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Release the connection mutex to make sure that
|
|
// we don't get a mutex level violation when the
|
|
// disconnect occurs. This connection will not go away
|
|
// since it's still referenced.
|
|
//
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
ConnectMutexOwned = FALSE;
|
|
|
|
//
|
|
// If there are no longer any valid connections in the system,
|
|
// blow the VC away.
|
|
//
|
|
|
|
if (NumberOfValidConnections == 0) {
|
|
|
|
//
|
|
// Disconnect the specific connection we want to
|
|
// invalidate.
|
|
//
|
|
|
|
CleanupTransportConnection(Irp, Connection, Connection->Server);
|
|
|
|
}
|
|
|
|
try_return(Status = STATUS_SUCCESS);
|
|
try_exit:NOTHING;
|
|
} finally {
|
|
|
|
if (ConnectMutexOwned) {
|
|
//
|
|
// Release the conection package mutext to allow requests to
|
|
// continue.
|
|
//
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
}
|
|
|
|
ExReleaseResource(&Connection->Server->SessionStateModifiedLock);
|
|
|
|
ExReleaseResource (&Connection->Server->RawResource);
|
|
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
VOID
|
|
CleanupTransportConnection(
|
|
IN PIRP Irp,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSERVERLISTENTRY Server
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine cleans up either the SpecialIpcConnection or the normal
|
|
connection.
|
|
|
|
Arguments:
|
|
|
|
IN PIRP Irp -
|
|
IN PCONNECTLISTENTRY Connection - Connection to cleanup.
|
|
IN PTRANSPORT_CONNECTION TransportConnection - SpeciaplIpc or normal
|
|
connection
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
DISCARDABLE_CODE(RdrConnectionDiscardableSection);
|
|
|
|
if (Server != NULL) {
|
|
|
|
//
|
|
// Acquire the outstanding requests resource to guarantee that all
|
|
// requests on this server have been completed. We need to
|
|
// do this before we blow away the connection. Note that it
|
|
// is safe to do this here because we already have the
|
|
// SessionStateModified lock, and thus no-one can make a new
|
|
// connection to the server valid.
|
|
//
|
|
// Also note that you cannot combine the outstanding requests
|
|
// resource and the session state modified lock.
|
|
//
|
|
// The reason is tied to how we timeout outstanding requests.
|
|
// When we timeout a request, we also queue a disconnection to
|
|
// make sure that any outstanding receives also complete in a
|
|
// timely fashion. Before we process the disconnect, we need
|
|
// to acquire SessionStateModified for exclusive access, and
|
|
// then issue the disconnect.
|
|
//
|
|
// If we combined SSM and OutstandingRequests, then we would not
|
|
// be able to drop the VC until after the request that we are
|
|
// timing out completed, and that won't complete until after we
|
|
// drop the VC.
|
|
//
|
|
// We cannot issue the disconnect outside of sessionstatemodified
|
|
// because we would introduce a window where a reconnect could
|
|
// occur before the disconnect was fully processed.
|
|
//
|
|
|
|
ACQUIRE_REQUEST_RESOURCE_EXCLUSIVE( Server, TRUE, 5 );
|
|
|
|
ASSERT (Server->NumberOfActiveEntries <= 1);
|
|
|
|
RdrInvalidateConnectionActiveSecurityEntries(Irp, Server, Connection, TRUE, 0);
|
|
|
|
//
|
|
// If this is the last reference to this server, and we
|
|
// haven't disconnected it already, blow away the
|
|
// connection to the server.
|
|
//
|
|
|
|
ACQUIRE_SPIN_LOCK(&RdrServerConnectionValidSpinLock, &OldIrql);
|
|
|
|
if (Server->ConnectionValid) {
|
|
|
|
Server->ConnectionValid = FALSE;
|
|
|
|
if (Server->ConnectionContext->TransportProvider != NULL) {
|
|
RELEASE_SPIN_LOCK(&RdrServerConnectionValidSpinLock, OldIrql);
|
|
|
|
RdrTdiDisconnect(Irp, Server);
|
|
} else {
|
|
RELEASE_SPIN_LOCK(&RdrServerConnectionValidSpinLock, OldIrql);
|
|
}
|
|
} else {
|
|
RELEASE_SPIN_LOCK(&RdrServerConnectionValidSpinLock, OldIrql);
|
|
}
|
|
|
|
|
|
RELEASE_REQUEST_RESOURCE( Server, 6 );
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef PAGING_OVER_THE_NET
|
|
VOID
|
|
CountPagingFiles(
|
|
IN PFCB Fcb,
|
|
IN PVOID Ctx
|
|
)
|
|
{
|
|
PDWORD Context = Ctx;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Walk the list of FCBs and check each ICB on the chain.
|
|
//
|
|
|
|
dprintf(DPRT_CONNECT, ("Checking FCB %wZ to see if it is a paging file..\n", &Fcb->FileName));
|
|
|
|
if (Fcb->Flags & FCB_PAGING_FILE) {
|
|
*Context += 1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
NTSTATUS
|
|
RdrDeleteConnection (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PUNICODE_STRING DeviceName OPTIONAL,
|
|
IN PSECURITY_ENTRY Se OPTIONAL,
|
|
IN ULONG Level
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will blast all the open files on a connection.
|
|
|
|
Arguments:
|
|
|
|
IN PIRP Irp - I/O request used for operations here.
|
|
IN PCONNECTLIST_ENTRY Connection - Supplies the connection to remove
|
|
IN PUNICODE_STRING DeviceName OPTIONAL - If not present, remove all files
|
|
If present, delete files on that connection
|
|
IN PSECURITY_ENTRY Se - Specifies the Se for the drive to delete.
|
|
IN ULONG Level - Supplies the force level for the disconnect.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
ULONG NumberOfOpenDirectories;
|
|
ULONG NumberOfOpenFiles;
|
|
ULONG NumberOfTreeConnections;
|
|
BOOLEAN ConnectMutexOwned = FALSE;
|
|
BOOLEAN ConnectLockOwned = FALSE;
|
|
BOOLEAN SpecialIpcAcquired = FALSE;
|
|
BOOLEAN ConnectionAcquired = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
RdrReferenceDiscardableCode(RdrFileDiscardableSection);
|
|
|
|
//
|
|
// Acquire the raw resource exclusively before we acquire the creation
|
|
// lock.
|
|
//
|
|
//
|
|
// We need to acquire the raw resource here before we acquire the
|
|
// creation lock.
|
|
//
|
|
// The reason for this is as follows:
|
|
//
|
|
//
|
|
// If there is an existing raw I/O going on, it will acquire the
|
|
// raw resource exclusively. We would then come in and acquire the
|
|
// creation lock, and block until the raw I/O completed.
|
|
//
|
|
// If the raw I/O caused a VC timeout, then the disconnect logic
|
|
// would attempt to acquire the CreationLock for exclusive access
|
|
// before dropping the VC, but would be unable to acquire the
|
|
// creation lock because this thread owned it. The raw I/O won't
|
|
// complete because the VC hasn't been dropped, and the create
|
|
// won't complete because the raw I/O hasn't completed, thus
|
|
// deadlocking the system.
|
|
//
|
|
// To avoid this problem, we acquier the raw resource here outside
|
|
// of the creation lock. This means that we will not acquire the
|
|
// creation lock until no raw I/O is outstanding on the VC.
|
|
//
|
|
|
|
ExAcquireResourceShared(&Connection->Server->RawResource, TRUE );
|
|
|
|
//
|
|
// Acquire the FCB creation lock to guarantee that no-one creates
|
|
// a file between the time when we determine the connection references
|
|
// and we invalidate the connection.
|
|
//
|
|
|
|
ExAcquireResourceExclusive(&Connection->Server->CreationLock, TRUE);
|
|
|
|
ConnectLockOwned = TRUE;
|
|
|
|
try {
|
|
RdrGetConnectionReferences (Connection, DeviceName, Se,
|
|
&NumberOfTreeConnections,
|
|
&NumberOfOpenDirectories,
|
|
&NumberOfOpenFiles);
|
|
|
|
if (Level != USE_LOTS_OF_FORCE) {
|
|
|
|
if (NumberOfOpenDirectories != 0) {
|
|
try_return(Status = STATUS_CONNECTION_IN_USE);
|
|
}
|
|
|
|
if (NumberOfOpenFiles != 0) {
|
|
try_return(Status = STATUS_FILES_OPEN);
|
|
}
|
|
}
|
|
|
|
#ifdef PAGING_OVER_THE_NET
|
|
//
|
|
// If there may be paging files on the connection, we cannot delete this
|
|
// connection.
|
|
//
|
|
|
|
if (Connection->Flags & CLE_PAGING_FILE) {
|
|
DWORD numberOfPagingFiles = 0;
|
|
|
|
//
|
|
// Scan the connection to see if any FCB's opened on the connection
|
|
// are paging files, and if so, fail the DeleteConnection.
|
|
//
|
|
|
|
RdrForeachFcbOnConnection(Connection, NoLock, CountPagingFiles, &numberOfPagingFiles);
|
|
|
|
if (numberOfPagingFiles != 0) {
|
|
try_return(Status = STATUS_CONNECTION_IN_USE);
|
|
} else {
|
|
RdrResetConnectlistFlag(Connection, CLE_PAGING_FILE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// Close any open files on the connection before we delete the
|
|
// tree connection.
|
|
//
|
|
|
|
//
|
|
// If there are any dormant or user files open on this connection,
|
|
// blow them away. Please note that this will also invalidate
|
|
// the ICB we are doing the RdrDeleteConnection on.
|
|
//
|
|
|
|
RdrInvalidateConnectionFiles(Irp, Connection, DeviceName, Se, TRUE);
|
|
|
|
//
|
|
// Now that we've closed down the appropriate set of files, see if
|
|
// we have any other tree connections open on the drive.
|
|
//
|
|
|
|
RdrGetConnectionReferences (Connection, NULL, // Device
|
|
NULL, // Security entry
|
|
&NumberOfTreeConnections,
|
|
&NumberOfOpenDirectories,
|
|
&NumberOfOpenFiles);
|
|
|
|
//
|
|
// If there are any other remaining connections on the file, or if
|
|
// there are still any open files on the connection, exit now.
|
|
//
|
|
|
|
if ((NumberOfTreeConnections > 1)
|
|
|
|
||
|
|
|
|
(NumberOfOpenFiles != 0)
|
|
|
|
||
|
|
|
|
(NumberOfOpenDirectories != 0)) {
|
|
|
|
//
|
|
// After invalidating all files on this connection belonging to
|
|
// this Se and DeviceName, we still have open files on this
|
|
// connection. If none of these open files belong to this Se, then
|
|
// we need to log off this Se from the server, or we'll leak
|
|
// sessions...
|
|
//
|
|
|
|
if (ARGUMENT_PRESENT(Se)) {
|
|
|
|
RdrGetConnectionReferences(
|
|
Connection,
|
|
NULL,
|
|
Se,
|
|
&NumberOfTreeConnections,
|
|
&NumberOfOpenDirectories,
|
|
&NumberOfOpenFiles);
|
|
|
|
if (NumberOfTreeConnections == 1 &&
|
|
NumberOfOpenDirectories == 0 &&
|
|
NumberOfOpenFiles == 0) {
|
|
|
|
RdrUserLogoff( Irp, Connection, Se );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
try_return(Status = STATUS_SUCCESS);
|
|
}
|
|
|
|
Status = RdrDisconnectConnection(Irp,
|
|
Connection,
|
|
TRUE,
|
|
Se); // Delete both connections
|
|
|
|
if (Connection->Deleted) {
|
|
//
|
|
// If the connection is already deleted, return an error after
|
|
// we've made sure that the VC/Tree connection has been nuked.
|
|
//
|
|
try_return(Status = STATUS_ALREADY_DISCONNECTED);
|
|
}
|
|
|
|
//
|
|
// Mark that the connection has been deleted.
|
|
//
|
|
|
|
Connection->Deleted = TRUE;
|
|
|
|
|
|
try_exit:NOTHING;
|
|
} finally {
|
|
|
|
if (ConnectMutexOwned) {
|
|
//
|
|
// Release the conection package mutext to allow requests to
|
|
// continue.
|
|
//
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
}
|
|
|
|
if (ConnectLockOwned) {
|
|
ExReleaseResource(&Connection->Server->CreationLock);
|
|
|
|
ExReleaseResource(&Connection->Server->RawResource);
|
|
|
|
}
|
|
|
|
RdrDereferenceDiscardableCode(RdrFileDiscardableSection);
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
VOID
|
|
LogonSessionTerminationHandler(
|
|
IN PVOID Context)
|
|
{
|
|
PLOGONSESSIONTERMINATIONCONTEXT LSTContext = Context;
|
|
PLUID LogonId = &LSTContext->LogonId;
|
|
PLIST_ENTRY ConnectionEntry;
|
|
PCONNECTLISTENTRY Connection;
|
|
PSERVERLISTENTRY *ServerList;
|
|
PSECURITY_ENTRY SecurityEntry;
|
|
PCONNECTLISTENTRY *ConnectionList;
|
|
ULONG ConnectionCount;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// First acquire the connection package exclusion mutex.
|
|
//
|
|
|
|
KeWaitForMutexObject(
|
|
&RdrDatabaseMutex,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
|
|
//
|
|
// Next, capture the list of connections.
|
|
//
|
|
|
|
for (ConnectionEntry = RdrConnectHead.Flink, ConnectionCount = 0;
|
|
ConnectionEntry != &RdrConnectHead ;
|
|
ConnectionEntry = ConnectionEntry->Flink,
|
|
ConnectionCount++) {
|
|
NOTHING;
|
|
}
|
|
|
|
if (ConnectionCount > 0) {
|
|
ConnectionList = ALLOCATE_POOL(
|
|
PagedPool,
|
|
ConnectionCount * sizeof(PCONNECTLISTENTRY),
|
|
POOL_LOGONTERMINATION);
|
|
|
|
if (ConnectionList != NULL) {
|
|
|
|
for (ConnectionEntry = RdrConnectHead.Flink, ConnectionCount = 0;
|
|
ConnectionEntry != &RdrConnectHead ;
|
|
ConnectionEntry = ConnectionEntry->Flink,
|
|
ConnectionCount++) {
|
|
|
|
ConnectionList[ConnectionCount] = CONTAINING_RECORD(
|
|
ConnectionEntry,
|
|
CONNECTLISTENTRY,
|
|
GlobalNext);
|
|
|
|
RdrReferenceConnection( ConnectionList[ConnectionCount] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ConnectionList = NULL;
|
|
|
|
}
|
|
|
|
//
|
|
// Release the connection package mutex.
|
|
//
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
//
|
|
// Now, scan the captured list of connections for active sessions with
|
|
// the logon-id that is terminating
|
|
//
|
|
|
|
if (ConnectionList != NULL) {
|
|
|
|
ULONG i;
|
|
|
|
ASSERT(ConnectionCount > 0);
|
|
|
|
for (i = 0; i < ConnectionCount; i++) {
|
|
|
|
SecurityEntry = RdrFindActiveSecurityEntry(
|
|
ConnectionList[i]->Server,
|
|
LogonId);
|
|
|
|
if (SecurityEntry != NULL) {
|
|
|
|
RdrUserLogoff(NULL, ConnectionList[i], SecurityEntry);
|
|
|
|
RdrDereferenceSecurityEntry( SecurityEntry->NonPagedSecurityEntry );
|
|
|
|
}
|
|
|
|
RdrDereferenceConnection(
|
|
NULL, // Irp
|
|
ConnectionList[i], // Connection
|
|
NULL, // Security Entry
|
|
FALSE); // Force disconnect if necessary
|
|
|
|
}
|
|
|
|
FREE_POOL( ConnectionList );
|
|
|
|
}
|
|
|
|
// Done
|
|
//
|
|
|
|
FREE_POOL( LSTContext );
|
|
|
|
}
|
|
|
|
VOID
|
|
RdrHandleLogonSessionTermination(
|
|
IN PLUID LogonId
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine handles a notification of a logon session being terminated
|
|
from the security subsystem. It essentially sends a logoff SMB to every
|
|
server to which this logon session has an active session.
|
|
|
|
Arguments:
|
|
|
|
LogonId - The logon id that is being logged off
|
|
|
|
Returns:
|
|
|
|
Nothing.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLOGONSESSIONTERMINATIONCONTEXT Ctx;
|
|
|
|
PAGED_CODE();
|
|
|
|
Ctx = ALLOCATE_POOL( NonPagedPool, sizeof(LOGONSESSIONTERMINATIONCONTEXT), POOL_LOGONTERMINATION);
|
|
|
|
if (Ctx != NULL) {
|
|
|
|
RtlZeroMemory(Ctx, sizeof(*Ctx));
|
|
|
|
Ctx->LogonId = *LogonId;
|
|
|
|
Ctx->WorkHeader.WorkerFunction = LogonSessionTerminationHandler;
|
|
|
|
RdrQueueToWorkerThread(
|
|
&RdrDeviceObject->IrpWorkQueue,
|
|
&Ctx->WorkHeader.WorkItem,
|
|
FALSE );
|
|
|
|
}
|
|
}
|
|
|
|
VOID
|
|
RdrReferenceServer (
|
|
IN PSERVERLISTENTRY Sle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will apply a new reference to a supplied server list.
|
|
|
|
Arguments:
|
|
|
|
IN PSERVERLISTENTRY ServerList - Supplies server to reference.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
--*/
|
|
{
|
|
IN KIRQL OldIrql;
|
|
|
|
DISCARDABLE_CODE(RdrConnectionDiscardableSection);
|
|
|
|
ACQUIRE_SPIN_LOCK(&RdrServerListSpinLock, &OldIrql);
|
|
|
|
Sle->RefCount += 1;
|
|
|
|
RELEASE_SPIN_LOCK(&RdrServerListSpinLock, OldIrql);
|
|
|
|
}
|
|
|
|
typedef struct _DEREF_SERVER_CONTEXT {
|
|
WORK_QUEUE_ITEM Item;
|
|
PSERVERLISTENTRY Server;
|
|
} DEREF_SERVER_CONTEXT, *PDEREF_SERVER_CONTEXT;
|
|
|
|
VOID
|
|
DelayedDereferenceServer(
|
|
IN PVOID Ctx
|
|
)
|
|
{
|
|
PDEREF_SERVER_CONTEXT Context = Ctx;
|
|
|
|
PAGED_CODE();
|
|
|
|
RdrDereferenceServer(NULL, Context->Server);
|
|
|
|
FREE_POOL(Context);
|
|
}
|
|
|
|
VOID
|
|
RdrScavengeServerEntries()
|
|
{
|
|
SERVERLISTENTRY *pAgedServerEntry;
|
|
PLIST_ENTRY pListEntry,pNextListEntry;
|
|
LIST_ENTRY AgedServersList;
|
|
KIRQL OldIrql;
|
|
|
|
InitializeListHead(&AgedServersList);
|
|
|
|
ACQUIRE_SPIN_LOCK(&RdrServerListSpinLock, &OldIrql);
|
|
|
|
pListEntry = RdrServerScavengerListHead.Flink;
|
|
while (pListEntry != &RdrServerScavengerListHead) {
|
|
pNextListEntry = pListEntry->Flink;
|
|
|
|
pAgedServerEntry = (PSERVERLISTENTRY)
|
|
CONTAINING_RECORD(
|
|
pListEntry,
|
|
SERVERLISTENTRY,
|
|
ScavengerList);
|
|
if ((pAgedServerEntry->LastConnectTime + FAILED_CONNECT_TIMEOUT < RdrCurrentTime) ||
|
|
(RdrData.Initialized != RdrStarted)) {
|
|
RemoveEntryList(pListEntry);
|
|
InsertTailList(&AgedServersList,pListEntry);
|
|
}
|
|
|
|
pListEntry = pNextListEntry;
|
|
}
|
|
|
|
RELEASE_SPIN_LOCK(&RdrServerListSpinLock, OldIrql);
|
|
|
|
while (!IsListEmpty(&AgedServersList)) {
|
|
pListEntry = RemoveHeadList(&AgedServersList);
|
|
|
|
pAgedServerEntry = (PSERVERLISTENTRY)
|
|
CONTAINING_RECORD(
|
|
pListEntry,
|
|
SERVERLISTENTRY,
|
|
ScavengerList);
|
|
|
|
RdrDereferenceServer(NULL,pAgedServerEntry);
|
|
}
|
|
}
|
|
|
|
VOID
|
|
RdrDereferenceServer (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PSERVERLISTENTRY ServerList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will decrement the reference count on the serverlist, and if
|
|
it goes to 0, will remove the serverlist
|
|
|
|
Arguments:
|
|
|
|
IN PSERVERLISTENTRY ServerList - Supplies server to decrement
|
|
reference. If the serverlist reference
|
|
count goes to 0, the connection to the
|
|
remote machine will be removed and the
|
|
serverlist will be freed.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
KIRQL OldIrql;
|
|
|
|
DISCARDABLE_CODE(RdrConnectionDiscardableSection);
|
|
|
|
ASSERT(ServerList->Signature == STRUCTURE_SIGNATURE_SERVERLISTENTRY);
|
|
|
|
//
|
|
// Early out if this is not removing the last reference to the server.
|
|
//
|
|
|
|
ACQUIRE_SPIN_LOCK(&RdrServerListSpinLock, &OldIrql);
|
|
|
|
if (ServerList->RefCount > 1) {
|
|
|
|
ServerList->RefCount -= 1;
|
|
|
|
RELEASE_SPIN_LOCK(&RdrServerListSpinLock, OldIrql);
|
|
|
|
return;
|
|
} else if ((ServerList->RefCount == 1) &&
|
|
(RdrData.Initialized == RdrStarted)) {
|
|
// Ensure that the Server entry has aged sufficiently before it can be thrown
|
|
// away. By allowing them to age, subsequent attempts to a failed server can
|
|
// be throttled back.
|
|
if (!NT_SUCCESS(ServerList->LastConnectStatus) &&
|
|
((ServerList->LastConnectTime + FAILED_CONNECT_TIMEOUT) > RdrCurrentTime)) {
|
|
// The Server list entry has not aged sufficiently. Insert it in the global
|
|
// list and return without finalizing now. The RdrpScavengeServerEntries will
|
|
// subsequently take away this reference.
|
|
|
|
InsertTailList(&RdrServerScavengerListHead,&ServerList->ScavengerList);
|
|
RELEASE_SPIN_LOCK(&RdrServerListSpinLock, OldIrql);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
RELEASE_SPIN_LOCK(&RdrServerListSpinLock, OldIrql);
|
|
|
|
//
|
|
// If we are not running at passive level, we need to queue this
|
|
// dereference request to a executive worker thread.
|
|
//
|
|
|
|
if (KeGetCurrentIrql() >= DISPATCH_LEVEL) {
|
|
|
|
PDEREF_SERVER_CONTEXT Context;
|
|
|
|
Context = ALLOCATE_POOL(NonPagedPoolMustSucceed, sizeof(DEREF_SERVER_CONTEXT), POOL_DEREFSERVERCTX);
|
|
|
|
Context->Server = ServerList;
|
|
|
|
ExInitializeWorkItem(&Context->Item, DelayedDereferenceServer, Context);
|
|
|
|
RdrQueueWorkItem(&Context->Item, CriticalWorkQueue);
|
|
|
|
return;
|
|
}
|
|
|
|
KeWaitForMutexObject(&RdrDatabaseMutex, Executive, KernelMode, FALSE, NULL);
|
|
|
|
ACQUIRE_SPIN_LOCK(&RdrServerListSpinLock, &OldIrql);
|
|
|
|
if (ServerList->RefCount) {
|
|
|
|
ServerList->RefCount -= 1;
|
|
|
|
dprintf(DPRT_CONNECT, ("Decrement reference to Server %lx, going to %lx\n", ServerList, ServerList->RefCount));
|
|
|
|
if (ServerList->RefCount == 0) {
|
|
|
|
//
|
|
// Now that we are done modifying the server list, we
|
|
// can release the serverlist spinlock.
|
|
//
|
|
|
|
RELEASE_SPIN_LOCK(&RdrServerListSpinLock, OldIrql);
|
|
|
|
dprintf(DPRT_CONNECT, ("Decrement reference to Server %wZ to 0", &ServerList->Text));
|
|
|
|
//
|
|
// The ServerList reference count just went to 0 remove it.
|
|
//
|
|
|
|
ASSERT(IsListEmpty(&ServerList->CLEHead));
|
|
|
|
//
|
|
// First we want to remove the ServerList from the global Server
|
|
// chain.
|
|
//
|
|
|
|
RemoveEntryList(&ServerList->GlobalNext);
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
//
|
|
// If this connection has any default security entries
|
|
// associated with it, remove the reference to them.
|
|
//
|
|
|
|
ExAcquireResourceExclusive(&RdrDefaultSeLock, TRUE);
|
|
|
|
while (!IsListEmpty(&ServerList->DefaultSeList)) {
|
|
PLIST_ENTRY SeEntry;
|
|
PSECURITY_ENTRY Se2;
|
|
|
|
SeEntry = RemoveHeadList(&ServerList->DefaultSeList);
|
|
|
|
Se2 = CONTAINING_RECORD(SeEntry, SECURITY_ENTRY, DefaultSeNext);
|
|
//RdrLog(( "c rmv s", NULL, 12, Se2, 0, ServerList, Se2->Connection, Se2->Server, 0,
|
|
// ServerList->DefaultSeList.Flink, ServerList->DefaultSeList.Blink,
|
|
// 0, 0,
|
|
// Se2->DefaultSeNext.Flink, Se2->DefaultSeNext.Blink ));
|
|
|
|
Se2->DefaultSeNext.Flink = NULL;
|
|
|
|
Se2->DefaultSeNext.Blink = NULL;
|
|
|
|
ExReleaseResource(&RdrDefaultSeLock);
|
|
|
|
//
|
|
// This security entry had better be inactive by now.
|
|
//
|
|
|
|
ASSERT (Se2->ActiveNext.Flink == NULL);
|
|
|
|
ASSERT (Se2->ActiveNext.Blink == NULL);
|
|
|
|
RdrDereferenceSecurityEntry(Se2->NonPagedSecurityEntry);
|
|
|
|
ExAcquireResourceExclusive(&RdrDefaultSeLock, TRUE);
|
|
|
|
}
|
|
|
|
ExReleaseResource(&RdrDefaultSeLock);
|
|
|
|
//
|
|
// CleanupTransportConnection It acquires the request resource,
|
|
// then calls RdrInvalidateConnectionActiveSecurityEntries,
|
|
// which acquires SessionStateModified. Since our lock
|
|
// ordering requires that SessionStateModified be acquired
|
|
// first, we need to acquire it here before calling
|
|
// CleanupTransportConnection.
|
|
//
|
|
|
|
ExAcquireResourceExclusive(&ServerList->SessionStateModifiedLock, TRUE);
|
|
CleanupTransportConnection(Irp, NULL, ServerList);
|
|
ExReleaseResource(&ServerList->SessionStateModifiedLock);
|
|
|
|
//
|
|
// We are decrementing this servers reference count to 0 -
|
|
// invalidate the security entries associated with the connection.
|
|
//
|
|
|
|
RdrInvalidateConnectionPotentialSecurityEntries(ServerList);
|
|
|
|
FreeServerList(ServerList);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
RELEASE_SPIN_LOCK(&RdrServerListSpinLock, OldIrql);
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
RELEASE_SPIN_LOCK(&RdrServerListSpinLock, OldIrql);
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
InternalError(("CloseServerRef: Decrement reference through zero"));
|
|
|
|
RdrInternalError(EVENT_RDR_SERVER_REFERENCE);
|
|
}
|
|
|
|
RELEASE_SPIN_LOCK(&RdrServerListSpinLock, OldIrql);
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
return;
|
|
}
|
|
typedef struct _GETCONNECTIONREFERENCES {
|
|
PUNICODE_STRING DeviceName;
|
|
PSECURITY_ENTRY Se;
|
|
ULONG NumberOfTreeConnections;
|
|
ULONG NumberOfOpenDirectories;
|
|
ULONG NumberOfOpenFiles;
|
|
} GETCONNECTIONREFERENCES, *PGETCONNECTIONREFERENCES;
|
|
|
|
VOID
|
|
RdrGetConnectionReferences(
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PUNICODE_STRING DeviceName OPTIONAL,
|
|
IN PSECURITY_ENTRY Se OPTIONAL,
|
|
OUT PULONG NumberOfTreeConnections OPTIONAL,
|
|
OUT PULONG NumberOfOpenDirectories OPTIONAL,
|
|
OUT PULONG NumberOfOpenFiles OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will return the number of references to a connection
|
|
|
|
Arguments:
|
|
|
|
IN PCONNECTLISTENTRY Connection - Specifies the connection to return info
|
|
IN PUNICODE_STRING DeviceName OPTIONAL - Only enumerate files on this drive
|
|
OUT PULONG NumberOfTreeConnections OPTIONAL - # of tree connects
|
|
OUT PULONG NumberOfOpenDirectories OPTIONAL - # of directories
|
|
OUT PULONG NumberOfOpenFiles OPTIONAL - Number of open files.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
GETCONNECTIONREFERENCES Context;
|
|
|
|
PAGED_CODE();
|
|
|
|
Context.DeviceName = DeviceName;
|
|
Context.Se = Se;
|
|
Context.NumberOfTreeConnections = 0;
|
|
Context.NumberOfOpenDirectories = 0;
|
|
Context.NumberOfOpenFiles = 0;
|
|
|
|
dprintf(DPRT_CONNECT, ("RdrGetConnectionReferences: Cle: %lx (\\%wZ\\%wZ)\n",
|
|
Connection, &Connection->Server->Text, &Connection->Text));
|
|
|
|
//
|
|
// Guarantee that we don't own any mutexes at this point.
|
|
//
|
|
|
|
RdrForeachFcbOnConnection(Connection, NoLock, GetFcbReferences, &Context);
|
|
|
|
if (ARGUMENT_PRESENT(NumberOfOpenFiles)) {
|
|
*NumberOfOpenFiles = Context.NumberOfOpenFiles;
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(NumberOfOpenDirectories)) {
|
|
*NumberOfOpenDirectories = Context.NumberOfOpenDirectories;
|
|
}
|
|
|
|
if (ARGUMENT_PRESENT(NumberOfTreeConnections)) {
|
|
*NumberOfTreeConnections = Context.NumberOfTreeConnections;
|
|
}
|
|
dprintf(DPRT_CONNECT, ("RdrGetConnectionReferences done.\n"));
|
|
}
|
|
|
|
VOID
|
|
GetFcbReferences(
|
|
IN PFCB Fcb,
|
|
IN PVOID Ctx
|
|
)
|
|
{
|
|
PGETCONNECTIONREFERENCES Context = Ctx;
|
|
PLIST_ENTRY IcbEntry;
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Walk the list of FCBs and check each ICB on the chain.
|
|
//
|
|
|
|
dprintf(DPRT_CONNECT, ("Checking file %wZ.\n", &Fcb->FileName));
|
|
|
|
if (Fcb->NumberOfOpens != 0) {
|
|
|
|
ExAcquireResourceShared(&Fcb->NonPagedFcb->InstanceChainLock, TRUE);
|
|
|
|
for (IcbEntry = Fcb->InstanceChain.Flink ;
|
|
IcbEntry != &Fcb->InstanceChain ;
|
|
IcbEntry = IcbEntry->Flink) {
|
|
|
|
PICB Icb = CONTAINING_RECORD(IcbEntry, ICB, InstanceNext);
|
|
|
|
dprintf(DPRT_CONNECT, (" Checking instance %lx. Opened on drive %wZ\n", Icb, &Icb->DeviceName));
|
|
|
|
if (!ARGUMENT_PRESENT(Context->DeviceName)
|
|
|
|
||
|
|
|
|
RtlEqualUnicodeString(Context->DeviceName, &Icb->DeviceName, TRUE)) {
|
|
|
|
//
|
|
// Only count files opened by this user on this connection
|
|
//
|
|
|
|
if (!ARGUMENT_PRESENT(Context->Se)
|
|
|
|
||
|
|
|
|
((Icb->Se != NULL) &&
|
|
RtlEqualLuid(&Context->Se->LogonId, &Icb->Se->LogonId))) {
|
|
|
|
//
|
|
// Only count files and directories that haven't been
|
|
// invalidated.
|
|
//
|
|
|
|
if (!(Icb->Flags & ICB_ERROR)) {
|
|
|
|
if (Icb->Type == Directory) {
|
|
dprintf(DPRT_CONNECT, ("Instance is a directory\n"));
|
|
Context->NumberOfOpenDirectories += 1;
|
|
} else if ((Icb->Type == DiskFile) ||
|
|
(Icb->Type == NamedPipe) ||
|
|
(Icb->Type == Com)) {
|
|
dprintf(DPRT_CONNECT, ("Instance is a file\n"));
|
|
Context->NumberOfOpenFiles += 1;
|
|
} else if ((Icb->Type == TreeConnect) ||
|
|
(Icb->Type == PrinterFile)) {
|
|
|
|
if (!(Icb->Flags & ICB_TCONCREATED)) {
|
|
|
|
if (Icb->Type == PrinterFile) {
|
|
|
|
dprintf(DPRT_CONNECT, ("Instance is a file\n"));
|
|
|
|
Context->NumberOfOpenFiles += 1;
|
|
|
|
} else {
|
|
|
|
dprintf(DPRT_CONNECT, ("Instance is a directory\n"));
|
|
|
|
Context->NumberOfOpenDirectories += 1;
|
|
}
|
|
|
|
} else {
|
|
|
|
dprintf(DPRT_CONNECT, ("Instance is a tree connection\n"));
|
|
|
|
Context->NumberOfTreeConnections += 1;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// Also count tree connections that HAVE been
|
|
// invalidated.
|
|
//
|
|
|
|
if (Icb->Flags & ICB_TCONCREATED) {
|
|
ASSERT (Icb->Type == TreeConnect);
|
|
|
|
dprintf(DPRT_CONNECT, ("Instance is an invalid tree connection\n"));
|
|
|
|
Context->NumberOfTreeConnections += 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ExReleaseResource(&Fcb->NonPagedFcb->InstanceChainLock);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
FindConnection (
|
|
IN PUNICODE_STRING ServerName,
|
|
IN PUNICODE_STRING ShareName,
|
|
IN PTRANSPORT Transport OPTIONAL,
|
|
OUT PCONNECTLISTENTRY *Connection,
|
|
IN ULONG Type
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will walk the connectlist database to find a connection
|
|
that matches the supplied string.
|
|
|
|
Arguments:
|
|
|
|
PUNICODE_STRING RemoteName - Supplies a pointer to a string describing the
|
|
remote servername and connection.
|
|
|
|
IN ULONG Type - Supplies the type of the connection to return.
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS value describing the state of the connection. If it could
|
|
not be found, STATUS_OBJECT_NAME_NOT_FOUND is returned.
|
|
|
|
Note:
|
|
It is possible for this routine to return a connection that is
|
|
in the initializing state, it is the callers responsibility to
|
|
wait for the connection to be fully established.
|
|
|
|
Also note that this routine cannot block waiting on a connection
|
|
to be established while it owns the connection database.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_OBJECT_NAME_NOT_FOUND; // Default error return.
|
|
PSERVERLISTENTRY ServerList = NULL; // ServerList representing computer
|
|
PCONNECTLISTENTRY ConnectTemp; // Temporary connectlist.
|
|
BOOLEAN ConnectMutexOwned;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// First claim the connectlist database mutex.
|
|
//
|
|
|
|
dprintf(DPRT_CONNECT, ("Connect to \\%wZ\\%wZ\n", ServerName, ShareName));
|
|
|
|
Status = NT_SUCCESS(KeWaitForMutexObject(&RdrDatabaseMutex, // Object to wait on
|
|
Executive, // Reason for waiting
|
|
KernelMode, // Processor Mode
|
|
FALSE, // Alertable
|
|
NULL)); // Timeout
|
|
|
|
ASSERT(NT_SUCCESS(Status));
|
|
|
|
ConnectMutexOwned = TRUE;
|
|
|
|
try {
|
|
PLIST_ENTRY ConnectEntry;
|
|
|
|
//
|
|
// We now own the ConnectList global mutex. We now want to find the
|
|
// serverlist that the connection is active on, then find the
|
|
// connectlist that this connection is on.
|
|
//
|
|
|
|
dprintf(DPRT_CONNECT, ("Connecting to server %wZ\n", ServerName));
|
|
|
|
Status = FindServerList(ServerName, Transport, &ServerList);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
//
|
|
// We successfully found a serverlist representing the
|
|
// remote server, now we want to wait for the server to
|
|
// finish initializing and check to see if the connection
|
|
// is active on the serverlist.
|
|
//
|
|
|
|
ASSERT(ServerList->Signature==STRUCTURE_SIGNATURE_SERVERLISTENTRY);
|
|
|
|
for (ConnectEntry = ServerList->CLEHead.Flink ;
|
|
ConnectEntry != &ServerList->CLEHead ;
|
|
ConnectEntry = ConnectEntry->Flink) {
|
|
|
|
ConnectTemp = CONTAINING_RECORD(ConnectEntry,
|
|
CONNECTLISTENTRY,
|
|
SiblingNext);
|
|
|
|
dprintf(DPRT_CONNECT, ("FindConnection %wZ==%wZ?\n", &ConnectTemp->Text, ShareName));
|
|
|
|
if (RtlEqualUnicodeString (&ConnectTemp->Text, ShareName, TRUE)) {
|
|
|
|
//
|
|
// Make sure that the guy we find matches the type we are
|
|
// looking for..
|
|
//
|
|
|
|
if ((Type != CONNECT_WILD)
|
|
|
|
&&
|
|
|
|
(ConnectTemp->Type != CONNECT_WILD)
|
|
|
|
&&
|
|
|
|
(ConnectTemp->Type != Type)) {
|
|
|
|
try_return(Status = STATUS_BAD_DEVICE_TYPE);
|
|
}
|
|
|
|
//
|
|
// We've found a connectlist that matches this path.
|
|
//
|
|
// Bump it's reference count and return it to our
|
|
// caller.
|
|
//
|
|
|
|
*Connection = ConnectTemp;
|
|
|
|
ConnectTemp->RefCount += 1;
|
|
|
|
dprintf(DPRT_CONNECT, ("New reference on connection \\%wZ\\%wZ, now %lx\n", &ConnectTemp->Server->Text, &ConnectTemp->Text, ConnectTemp->RefCount));
|
|
|
|
try_return(Status = STATUS_SUCCESS);
|
|
}
|
|
}
|
|
|
|
//
|
|
// We were unable to find a connectlist that matches the
|
|
// input path. Return an error to the caller.
|
|
//
|
|
|
|
dprintf(DPRT_CONNECT, ("ConnectList not found\n",0));
|
|
|
|
try_return(Status = STATUS_OBJECT_NAME_NOT_FOUND);
|
|
|
|
try_exit:NOTHING;
|
|
} finally {
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
*Connection = NULL;
|
|
}
|
|
|
|
//
|
|
// If we found a connection, we can dereference the server, since
|
|
// the connection references the serverlist. If we didn't find
|
|
// the connection, we can dereference the serverlist if one was
|
|
// found since nothing references the server.
|
|
//
|
|
|
|
if (ServerList != NULL) {
|
|
RdrDereferenceServer(NULL, ServerList);
|
|
}
|
|
|
|
//
|
|
// We're all done, release the connectlist database mutex.
|
|
//
|
|
|
|
if (ConnectMutexOwned) {
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
}
|
|
dprintf(DPRT_CONNECT,("FindConnection finished, status=%X\n",Status));
|
|
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
AllocateConnection (
|
|
IN PUNICODE_STRING ServerName,
|
|
IN PUNICODE_STRING ShareName,
|
|
IN PTRANSPORT Transport OPTIONAL,
|
|
OUT PCONNECTLISTENTRY *Connection
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will allocate a new ConnectList structure and link in the
|
|
structures into the global connectlist database.
|
|
|
|
|
|
Arguments:
|
|
|
|
IN PUNICODE_STRING ConnectionName - Supplies the name of the remote server/share
|
|
OUT PCONNECTION Connection - Returns a pointer to the allocated CList
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Final status of operation
|
|
|
|
Note:
|
|
This code is called protected by the Connection Mutex, if it isn't, bad
|
|
things could happen.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PSERVERLISTENTRY ServerList = NULL;
|
|
PCONNECTLISTENTRY CLE;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Make sure that there are no connections with the same name as this one.
|
|
//
|
|
|
|
ASSERT (!NT_SUCCESS(FindConnection(ServerName, ShareName, Transport, &CLE, (ULONG)CONNECT_WILD)));
|
|
|
|
//
|
|
// Try to find a serverlist whose name matches the new connection.
|
|
//
|
|
|
|
if (!NT_SUCCESS(FindServerList(ServerName, Transport, &ServerList))) {
|
|
|
|
//
|
|
// We couldn't find an existing ServerList that matches this
|
|
// server, so we want to allocate a new ServerList.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status = AllocateServerList(ServerName, Transport, &ServerList))) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status = AllocateConnectList(ShareName, ServerList,
|
|
&CLE))) {
|
|
|
|
RdrDereferenceServer(NULL, ServerList);
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
*Connection = CLE;
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
FindServerList (
|
|
IN PUNICODE_STRING ServerName,
|
|
IN PTRANSPORT Transport,
|
|
OUT PSERVERLISTENTRY *ServerList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine walks the global ServerList chain trying to find a
|
|
serverlist that matches the input criteria.
|
|
|
|
Arguments:
|
|
|
|
IN PUNICODE_STRING ServerName, - Supplies the name of the server to find
|
|
OUT PSERVERLISTENTRY *ServerList - Returns the serverlist if it is found
|
|
|
|
Return Value:
|
|
|
|
Status of operation, success/failure
|
|
|
|
Note:
|
|
Assumes that the RdrDatabaseMutex is claimed.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY ServerEntry;
|
|
PSERVERLISTENTRY ServerListTemp;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Re-acquire the lock protecting the chain to allow us to walk to the
|
|
// next entry on the chain.
|
|
//
|
|
|
|
KeWaitForMutexObject(&RdrDatabaseMutex, Executive,
|
|
KernelMode, FALSE, NULL);
|
|
|
|
for (ServerEntry= RdrServerHead.Flink ;
|
|
ServerEntry != &RdrServerHead ;
|
|
ServerEntry= ServerEntry->Flink ){
|
|
|
|
ServerListTemp =CONTAINING_RECORD(ServerEntry,SERVERLISTENTRY,GlobalNext);
|
|
|
|
dprintf(DPRT_CONNECT, ("FindServerList %wZ==%wZ?\n", &ServerListTemp->Text, ServerName));
|
|
|
|
if (RtlEqualUnicodeString(&ServerListTemp->Text, ServerName, TRUE) &&
|
|
ServerListTemp->SpecificTransportProvider == Transport) {
|
|
|
|
//
|
|
// We found a serverlist that matches the input serverlist, return
|
|
// it.
|
|
//
|
|
|
|
*ServerList = ServerListTemp;
|
|
|
|
//
|
|
// We found an existing server list entry, we will be
|
|
// establishing a new reference to the server here.
|
|
//
|
|
|
|
|
|
RdrReferenceServer(ServerListTemp);
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
}
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
return STATUS_OBJECT_NAME_NOT_FOUND;
|
|
}
|
|
|
|
NTSTATUS
|
|
RdrForeachServer (
|
|
IN PRDR_ENUM_SERVER_CALLBACK Callback,
|
|
IN PVOID CallbackContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine enumerates the active server list entries and calls back
|
|
the callback routine for each server.
|
|
|
|
Arguments:
|
|
|
|
IN PRDR_ENUM_SERVER_CALLBACK Callback - Supplies the callback routine.
|
|
IN PVOID CallbackContext - Supplies a context structure for the callback.
|
|
|
|
Return Value:
|
|
|
|
STATUS.
|
|
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY ServerEntry;
|
|
PSERVERLISTENTRY ServerListTemp;
|
|
PLIST_ENTRY NextEntry;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Acquire the database mutex to allow us to walk to the
|
|
// next entry on the chain.
|
|
//
|
|
|
|
KeWaitForMutexObject(&RdrDatabaseMutex, Executive,
|
|
KernelMode, FALSE, NULL);
|
|
|
|
for (ServerEntry= RdrServerHead.Flink ;
|
|
ServerEntry != &RdrServerHead ;
|
|
ServerEntry = NextEntry ){
|
|
|
|
ServerListTemp =CONTAINING_RECORD(ServerEntry,SERVERLISTENTRY,GlobalNext);
|
|
|
|
RdrReferenceServer(ServerListTemp);
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
//
|
|
// Call the supplied callback routine.
|
|
//
|
|
|
|
Callback(ServerListTemp, CallbackContext);
|
|
|
|
KeWaitForMutexObject(&RdrDatabaseMutex, Executive,
|
|
KernelMode, FALSE, NULL);
|
|
|
|
//
|
|
// Step to the next entry in the chain.
|
|
//
|
|
|
|
NextEntry = ServerEntry->Flink;
|
|
|
|
//
|
|
// Dereference the old server list entry.
|
|
//
|
|
|
|
RdrDereferenceServer(NULL, ServerListTemp);
|
|
|
|
}
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
//
|
|
// Routines to allocate and free connection related structures
|
|
//
|
|
//
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
AllocateConnectList (
|
|
IN PUNICODE_STRING ShareName,
|
|
IN PSERVERLISTENTRY ServerList,
|
|
OUT PCONNECTLISTENTRY *ConnectList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates storage for a new ConnectList structure and links
|
|
it into the global database.
|
|
|
|
Arguments:
|
|
|
|
IN PUNICODE_STRING RemoteName - Supplies the name of the remote resource
|
|
IN PSERVERLISTENTRY ServerList - Supplies the ServerList this ConnectList
|
|
is associated with
|
|
OUT PCONNECTLISTENTRY ConnectList - Returns a pointer to the new
|
|
connectlist
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of Connection allocation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PCONNECTLISTENTRY Connect;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Allocate paged pool to hold the ConnectList structure.
|
|
//
|
|
|
|
Connect = ALLOCATE_POOL (PagedPool, sizeof(CONNECTLISTENTRY), POOL_CLE);
|
|
|
|
if (Connect == NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
RtlZeroMemory( Connect, sizeof( CONNECTLISTENTRY ) );
|
|
|
|
Connect->Signature = STRUCTURE_SIGNATURE_CONNECTLISTENTRY;
|
|
Connect->Size = sizeof(CONNECTLISTENTRY);
|
|
|
|
#ifdef NOTIFY
|
|
FsRtlNotifyInitializeSync( &Connect->NotifySync );
|
|
#endif
|
|
|
|
Status = RdrpDuplicateUnicodeStringWithString(&Connect->Text, ShareName, PagedPool, FALSE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
#ifdef NOTIFY
|
|
FsRtlNotifyUninitializeSync( &Connect->NotifySync );
|
|
#endif
|
|
FREE_POOL(Connect);
|
|
return Status;
|
|
}
|
|
|
|
InitializeListHead(&Connect->DefaultSeList);
|
|
|
|
//
|
|
// Initialize the connectlist per ICB chain and spinlock
|
|
//
|
|
|
|
InitializeListHead(&Connect->FcbChain);
|
|
|
|
//
|
|
// Link this ConnectList into the two chains that hold it, and
|
|
// initialize the ServerList back reference.
|
|
//
|
|
|
|
InsertTailList(&ServerList->CLEHead, &Connect->SiblingNext);
|
|
|
|
InsertTailList(&RdrConnectHead, &Connect->GlobalNext);
|
|
|
|
Connect->Server = ServerList;
|
|
|
|
//
|
|
// Initialize the ConnectList reference count to 1
|
|
//
|
|
|
|
Connect->RefCount = 1;
|
|
|
|
Connect->SerialNumber = RdrConnectionSerialNumber ++;
|
|
|
|
Connect->CachedValidCheckPath.Buffer = (PUSHORT)(&Connect->CachedValidCheckPath + 1);
|
|
Connect->CachedValidCheckPath.MaximumLength = MAX_PATH * sizeof( WCHAR );
|
|
|
|
Connect->CachedInvalidPath.Buffer = (PUSHORT)(&Connect->CachedInvalidPath + 1 );
|
|
Connect->CachedInvalidPath.MaximumLength = MAX_PATH * sizeof( WCHAR );
|
|
|
|
//
|
|
// Initialize the connection type to CONNECT_WILD until we know what
|
|
// type it really is.
|
|
//
|
|
|
|
Connect->Type = (ULONG)CONNECT_WILD;
|
|
|
|
#ifdef NOTIFY
|
|
//
|
|
// Initialize list holding dir notify.
|
|
//
|
|
|
|
InitializeListHead(&Connect->DirNotifyList);
|
|
|
|
#endif
|
|
|
|
//
|
|
// Return the ConnectList to the caller.
|
|
//
|
|
|
|
*ConnectList = Connect;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
AllocateServerList (
|
|
IN PUNICODE_STRING ServerName,
|
|
IN PTRANSPORT Transport OPTIONAL,
|
|
OUT PSERVERLISTENTRY *ServerList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates memory for a new ServerList.
|
|
|
|
Arguments:
|
|
|
|
IN PUNICODE_STRING ServerName, - Supplies the name we are to connect to.
|
|
OUT PSERVERLISTENTRY ServerList - Returns a pointer to the newly
|
|
allocated ServerList
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of allocation.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PSERVERLISTENTRY Server = NULL;
|
|
BOOLEAN ResourceInitialized = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
RdrReferenceDiscardableCode(RdrConnectionDiscardableSection);
|
|
|
|
// DbgBreakPoint();
|
|
try {
|
|
|
|
//
|
|
// Allocate non-paged pool to hold the ServerList structure.
|
|
//
|
|
|
|
Server = ALLOCATE_POOL (NonPagedPool, sizeof(SERVERLISTENTRY), POOL_SLE);
|
|
|
|
if (Server == NULL) {
|
|
try_return(Status = STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
RtlZeroMemory( Server, sizeof(SERVERLISTENTRY) );
|
|
|
|
Server->Signature = STRUCTURE_SIGNATURE_SERVERLISTENTRY;
|
|
Server->Size = sizeof(SERVERLISTENTRY);
|
|
|
|
//
|
|
// Initialize the ConnectList chain.
|
|
//
|
|
|
|
InitializeListHead(&Server->CLEHead);
|
|
|
|
//
|
|
// Set the ServerList reference count to 1.
|
|
//
|
|
|
|
Server->RefCount = 1;
|
|
|
|
//
|
|
// Copy the remote server name into the ServerList
|
|
//
|
|
|
|
Status = RdrpDuplicateUnicodeStringWithString(&Server->Text, ServerName, NonPagedPool, FALSE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
//
|
|
// Initialize the default server capabilties.
|
|
//
|
|
|
|
Server->Capabilities = DF_CORE | DF_LANMAN20 | DF_LONGNAME;
|
|
|
|
//
|
|
// Assume for now that the server supports user security.
|
|
//
|
|
|
|
Server->UserSecurity = TRUE;
|
|
|
|
if (ARGUMENT_PRESENT(Transport)) {
|
|
RdrReferenceTransport(Transport->NonPagedTransport);
|
|
}
|
|
|
|
Server->SpecificTransportProvider = Transport;
|
|
|
|
Server->InCancel = FALSE;
|
|
|
|
//
|
|
// Start out assuming we can potentially do large RAW reads and writes
|
|
//
|
|
Server->RawReadMaximum = Server->RawWriteMaximum = 64*1024;
|
|
|
|
InitializeListHead(&Server->DefaultSeList);
|
|
|
|
// KeInitializeEvent(&Server->SpecialIpcSynchronizationLock, SynchronizationEvent, TRUE);
|
|
|
|
ExInitializeResource(&Server->CreationLock);
|
|
|
|
ExInitializeResource(&Server->SessionStateModifiedLock);
|
|
|
|
ResourceInitialized = TRUE;
|
|
|
|
Status = RdrInitializeTransportConnection(Server);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status)
|
|
}
|
|
|
|
//
|
|
// Insert the new ServerList at the head of the global ServerList
|
|
// chain.
|
|
//
|
|
|
|
InsertTailList(&RdrServerHead, &Server->GlobalNext);
|
|
|
|
Server->LastConnectStatus = STATUS_SUCCESS;
|
|
Server->LastConnectTime = (ULONG)-FAILED_CONNECT_TIMEOUT;
|
|
|
|
*ServerList = Server;
|
|
|
|
try_exit:NOTHING;
|
|
} finally {
|
|
if (!NT_SUCCESS(Status)) {
|
|
if (Server != NULL) {
|
|
if (Server->Text.Buffer != NULL) {
|
|
FREE_POOL(Server->Text.Buffer);
|
|
}
|
|
|
|
if (ResourceInitialized) {
|
|
ExDeleteResource(&Server->CreationLock);
|
|
ExDeleteResource(&Server->SessionStateModifiedLock);
|
|
}
|
|
|
|
FREE_POOL(Server);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DBGSTATIC
|
|
VOID
|
|
FreeServerList (
|
|
IN PSERVERLISTENTRY ServerList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine returns the memory and locks associated with a server_list to
|
|
pool.
|
|
|
|
|
|
Arguments:
|
|
|
|
IN PSERVERLISTENTRY ServerList - Supplies a pointer to the ServerList to free.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PAGED_CODE();
|
|
|
|
dprintf(DPRT_CONNECT, ("FreeServerList %lx\n", ServerList));
|
|
|
|
ASSERT (IsListEmpty(&ServerList->CLEHead));
|
|
|
|
ASSERT (IsListEmpty(&ServerList->DefaultSeList));
|
|
|
|
ASSERT (ServerList->RefCount == 0);
|
|
|
|
ASSERT (IsListEmpty(&ServerList->ActiveSecurityList));
|
|
ASSERT (IsListEmpty(&ServerList->PotentialSecurityList));
|
|
|
|
ExDeleteResource(&ServerList->RawResource);
|
|
ExDeleteResource(&ServerList->OutstandingRequestResource);
|
|
|
|
RdrUninitializeSmbExchangeForConnection(ServerList);
|
|
|
|
FREE_POOL(ServerList->Text.Buffer);
|
|
|
|
#ifdef _CAIRO_
|
|
if (ServerList->Principal.Length != 0) {
|
|
FREE_POOL(ServerList->Principal.Buffer);
|
|
}
|
|
#endif // _CAIRO_
|
|
|
|
if(ServerList->DomainName.Buffer)
|
|
{
|
|
FREE_POOL(ServerList->DomainName.Buffer);
|
|
}
|
|
|
|
ExDeleteResource(&ServerList->SessionStateModifiedLock);
|
|
|
|
if (ServerList->SpecificTransportProvider != NULL) {
|
|
RdrReferenceDiscardableCode(RdrVCDiscardableSection);
|
|
|
|
RdrDereferenceTransport(ServerList->SpecificTransportProvider->NonPagedTransport);
|
|
|
|
RdrDereferenceDiscardableCode(RdrVCDiscardableSection);
|
|
}
|
|
|
|
//
|
|
// Uninitialize the create/enum resource.
|
|
//
|
|
|
|
ExDeleteResource(&ServerList->CreationLock);
|
|
|
|
FREE_POOL(ServerList);
|
|
|
|
RdrDereferenceDiscardableCode(RdrConnectionDiscardableSection);
|
|
}
|
|
|
|
DBGSTATIC
|
|
VOID
|
|
FreeConnectList (
|
|
IN PCONNECTLISTENTRY ConnectList
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine frees up the storage used for a ConnectList. It assumes that
|
|
the given ConnectList has been removed from all associated chains.
|
|
|
|
Arguments:
|
|
|
|
IN PCONNECTLISTENTRY ConnectList - Supplies a pointer to the ConnectList to
|
|
free
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
PAGED_CODE();
|
|
|
|
ASSERT (IsListEmpty(&ConnectList->FcbChain));
|
|
|
|
ASSERT (IsListEmpty(&ConnectList->DefaultSeList));
|
|
|
|
ASSERT (ConnectList->RefCount == 0);
|
|
|
|
//
|
|
// Free the ConnectList's name text
|
|
//
|
|
|
|
FREE_POOL(ConnectList->Text.Buffer);
|
|
|
|
#ifdef NOTIFY
|
|
FsRtlNotifyUninitializeSync( &ConnectList->NotifySync );
|
|
#endif
|
|
|
|
//
|
|
// Free up the actual ConnectList structure.
|
|
//
|
|
|
|
FREE_POOL(ConnectList);
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
//
|
|
// Private support routines for connection module
|
|
//
|
|
//
|
|
|
|
|
|
//
|
|
//
|
|
//
|
|
// Connection oriented SMB processing
|
|
//
|
|
//
|
|
//
|
|
|
|
//
|
|
//
|
|
//
|
|
// Handling of negotiate SMB protocol.
|
|
//
|
|
//
|
|
//
|
|
//
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
CallServer (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes a connection to a remote server. It will call
|
|
the server and exchange a negotiate protocol SMB with the remote server
|
|
.
|
|
Arguments:
|
|
|
|
IN PSERVERLISTENTRY Server - Supplies the name of the server to call
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of the resultant operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUCHAR Bufferp;
|
|
PREQ_NEGOTIATE Negotiate;
|
|
NEGOTIATECONTEXT Context;
|
|
PSMB_BUFFER SMBBuffer = NULL;
|
|
PSMB_HEADER SendSMB;
|
|
PMDL SendMDL;
|
|
ULONG SendLength;
|
|
USHORT i;
|
|
PSERVERLISTENTRY Server = Connection->Server;
|
|
LARGE_INTEGER CurrentTime;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Connect to the remote server now. First we establish a VC to the
|
|
// server. We use the ServerListEntry describing the session as the
|
|
// context information that will be returned by the transport provider.
|
|
//
|
|
|
|
//
|
|
// Hook the transport provider to the server in case the connection
|
|
// worked and then immediately was disconnected.
|
|
//
|
|
|
|
#ifdef RASAUTODIAL
|
|
//
|
|
// If there are no transport bindings, then
|
|
// we alert the automatic connection driver
|
|
// to see if it would like to bring up a
|
|
// connection. When it completes, we continue
|
|
// with the remote server connection process.
|
|
//
|
|
if (fAcdLoadedG &&
|
|
RdrNoTransportBindings())
|
|
{
|
|
RdrAttemptAutoDial(&Server->Text);
|
|
}
|
|
#endif // RASAUTODIAL
|
|
|
|
if (Server->SpecificTransportProvider) {
|
|
Status = RdrTdiConnectOnTransport (Irp, Server->SpecificTransportProvider, &Server->Text, Server);
|
|
|
|
} else {
|
|
Status = RdrTdiConnectToServer (Irp, &Server->Text, Server);
|
|
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// The connection didn't work, return the appropriate error
|
|
//
|
|
|
|
return Status;
|
|
}
|
|
|
|
Context.DomainName.Buffer = 0;
|
|
|
|
//
|
|
// We now have a VC with the remote server - find out the NB and IP
|
|
// address, if any, of the server. This will succeed only if we went
|
|
// over NetBT.
|
|
//
|
|
|
|
Server->NBName.Length = 0;
|
|
Server->NBName.MaximumLength = 16;
|
|
Server->NBName.Buffer = Server->NBNameBuffer;
|
|
|
|
if (NT_SUCCESS(RdrQueryServerAddresses(
|
|
Server, &Server->NBName, &Server->IPAddress))) {
|
|
Server->Flags |= SLE_HAS_IP_ADDR;
|
|
} else {
|
|
Server->Flags &= ~SLE_HAS_IP_ADDR;
|
|
}
|
|
|
|
// (Re)initialize the maximum commands for this server appropriately.
|
|
// Note that we have to do this here in case the connection is being
|
|
// reused -- we need to reinitialize the MPX table because some servers
|
|
// return bogus MIDs in the negotiate response.
|
|
//
|
|
|
|
Status = RdrUpdateSmbExchangeForConnection(Server, 1, 1);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto ReturnError;
|
|
}
|
|
|
|
//
|
|
// Build a negotiate SMB and exchange it with the remote
|
|
// server.
|
|
//
|
|
|
|
Server->MaximumRequests = 1;
|
|
|
|
if ((SMBBuffer = RdrAllocateSMBBuffer())==NULL) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto ReturnError;
|
|
}
|
|
|
|
//
|
|
// Set common thingies in the header of the SMB buffer
|
|
//
|
|
|
|
SendSMB = (PSMB_HEADER )SMBBuffer->Buffer;
|
|
|
|
//
|
|
// Build the Negotiate SMB
|
|
//
|
|
|
|
SendSMB->Command = SMB_COM_NEGOTIATE;
|
|
|
|
Negotiate = (PREQ_NEGOTIATE ) (SendSMB+1);
|
|
|
|
Negotiate->WordCount = 0;
|
|
Bufferp = Negotiate->Buffer;
|
|
|
|
for (i=0;i<RdrNumDialects;i++) {
|
|
PCHAR Protocolp = RdrNegotiateDialect[i].DialectString;
|
|
*Bufferp++ = 0x02; // Stick dialect indication in buffer
|
|
strcpy(Bufferp, Protocolp); // Copy dialect string into SMB.
|
|
Bufferp += strlen(Protocolp)+1; // Bump dialect pointer
|
|
}
|
|
|
|
//
|
|
// Set the BCC field in the SMB to indicate the number of bytes of
|
|
// protocol we've put in the negotiate.
|
|
//
|
|
|
|
SmbPutUshort(&Negotiate->ByteCount, (USHORT )(Bufferp-(PUCHAR )(Negotiate->Buffer)));
|
|
|
|
SendLength = Bufferp-(PUCHAR )SendSMB;
|
|
|
|
SendMDL = SMBBuffer->Mdl;
|
|
SendMDL->ByteCount = SendLength;
|
|
|
|
//
|
|
// Next clean out and initailize the context structure.
|
|
//
|
|
|
|
Context.Header.Type = CONTEXT_NEGOTIATE;
|
|
Context.Header.TransferSize = SendLength + sizeof(RESP_NT_NEGOTIATE) + CRYPT_TXT_LEN;
|
|
|
|
//
|
|
// Almost ready to go. But first, allocate some space to hold
|
|
// the returned domain name.
|
|
//
|
|
|
|
Context.DomainName.Buffer = ExAllocatePool(NonPagedPool,
|
|
MAX_PATH * sizeof(WCHAR));
|
|
if(Context.DomainName.Buffer)
|
|
{
|
|
Context.DomainName.MaximumLength = MAX_PATH * sizeof(WCHAR);
|
|
Context.DomainName.Buffer[0] = 0;
|
|
}
|
|
else
|
|
{
|
|
Context.DomainName.MaximumLength = 0;
|
|
}
|
|
|
|
Status = RdrNetTranceiveWithCallback(
|
|
NT_RECONNECTING | NT_NOCONNECTLIST | NT_CANNOTCANCEL,
|
|
Irp, // Irp
|
|
Connection, // Server to exchange SMB's on.
|
|
SendMDL, // MDL for send
|
|
&Context, // Negotiate response context.
|
|
NegotiateCallback,
|
|
Se,
|
|
NULL);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto ReturnError;
|
|
}
|
|
|
|
ASSERT(Context.Header.ErrorType==NoError);
|
|
|
|
dprintf(DPRT_CONNECT, ("Negotiate complete.\n"));
|
|
dprintf(DPRT_CONNECT, ("Response:\n"));
|
|
dprintf(DPRT_CONNECT,(" Dialect: %d\n", Context.DialectIndex));
|
|
dprintf(DPRT_CONNECT,(" LockRead: %d\n", Context.SupportsLockRead));
|
|
dprintf(DPRT_CONNECT,(" Raw Read:%d Raw Write: %d\n", Context.SupportsRawRead, Context.SupportsRawWrite));
|
|
dprintf(DPRT_CONNECT,(" User Security:%d\n",Context.UserSecurity));
|
|
dprintf(DPRT_CONNECT,(" BufferSize:%d\n", Context.BufferSize));
|
|
dprintf(DPRT_CONNECT,(" MaximumRequests:%d\n", Context.MaximumRequests));
|
|
dprintf(DPRT_CONNECT,(" MaximumVCs:%d\n", Context.MaximumVCs));
|
|
dprintf(DPRT_CONNECT,(" SessionKey:%lx\n", Context.SessionKey));
|
|
dprintf(DPRT_CONNECT,(" TimeZone:%d\n", Context.TimeZone));
|
|
dprintf(DPRT_CONNECT,(" CryptKeyLength:%d\n", Context.CryptKeyLength));
|
|
|
|
//
|
|
// We successfully negotiated a dialect with the remote server.
|
|
//
|
|
// Fill in the fields in the ServerList to match the negotiate
|
|
// response.
|
|
//
|
|
|
|
Server->Capabilities =RdrNegotiateDialect[Context.DialectIndex].DialectFlags;
|
|
if (Server->Capabilities & DF_NTNEGOTIATE) {
|
|
|
|
if (Context.Capabilities & CAP_RAW_MODE) {
|
|
Server->SupportsRawRead = TRUE;
|
|
Server->SupportsRawWrite = TRUE;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_UNICODE) {
|
|
Server->Capabilities |= DF_UNICODE;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_LARGE_FILES) {
|
|
Server->Capabilities |= DF_LARGE_FILES;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_NT_SMBS) {
|
|
Server->Capabilities |= DF_NT_SMBS | DF_NT_FIND;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_NT_FIND) {
|
|
Server->Capabilities |= DF_NT_FIND;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_RPC_REMOTE_APIS) {
|
|
Server->Capabilities |= DF_RPC_REMOTE;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_NT_STATUS) {
|
|
Server->Capabilities |= DF_NT_STATUS;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_LEVEL_II_OPLOCKS) {
|
|
Server->Capabilities |= DF_OPLOCK_LVL2;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_LOCK_AND_READ) {
|
|
Server->Capabilities |= DF_LOCKREAD;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_DFS) {
|
|
Server->Capabilities |= DF_DFSAWARE;
|
|
}
|
|
|
|
if (Context.Capabilities & (CAP_QUADWORD_ALIGNED|CAP_LARGE_READX)) {
|
|
Server->Capabilities |= DF_NT_40;
|
|
}
|
|
|
|
if (Context.Capabilities & CAP_LARGE_READX ) {
|
|
Server->Capabilities |= DF_LARGE_READX;
|
|
}
|
|
|
|
} else {
|
|
|
|
Server->SupportsRawRead = Context.SupportsRawRead;
|
|
|
|
Server->SupportsRawWrite = Context.SupportsRawWrite;
|
|
|
|
}
|
|
|
|
//
|
|
// If this is a LM 1.0 or 2.0 server (ie a non NT server), we remember the
|
|
// timezone and bias our time based on this value.
|
|
//
|
|
// The redirector assumes that all times from these servers are local time
|
|
// for the server, and converts them to local time using this bias. It
|
|
// then tells the user the local time for the file on the server.
|
|
//
|
|
|
|
if ((Context.ServerTime.HighPart != 0) &&
|
|
(Context.ServerTime.LowPart != 0)) {
|
|
|
|
if ((Server->Capabilities & DF_LANMAN10) &&
|
|
!(Server->Capabilities & DF_NTNEGOTIATE)) {
|
|
|
|
#define ONE_MINUTE_IN_TIME (10 * 1000 * 1000 * 60)
|
|
|
|
LARGE_INTEGER Workspace;
|
|
BOOLEAN Negated = FALSE;
|
|
|
|
KeQuerySystemTime(&CurrentTime);
|
|
|
|
Workspace.QuadPart = CurrentTime.QuadPart - Context.ServerTime.QuadPart;
|
|
|
|
if ( Workspace.QuadPart < 0) {
|
|
// avoid using -ve large integers to routines that accept only unsigned
|
|
Workspace.QuadPart = -Workspace.QuadPart;
|
|
Negated = TRUE;
|
|
}
|
|
|
|
//
|
|
// Workspace has the exact difference in 100ns intervals
|
|
// between the server and redirector times. To remove the minor
|
|
// difference between the time settings on the two machines we
|
|
// round the Bias to the nearest 30 minutes.
|
|
//
|
|
// Calculate ((exact bias+15minutes)/30minutes)* 30minutes
|
|
// then convert back to the bias time.
|
|
//
|
|
|
|
Workspace.QuadPart += Int32x32To64(ONE_MINUTE_IN_TIME, 15);
|
|
|
|
// Workspace is now exact bias + 15 minutes in 100ns units
|
|
|
|
Workspace.QuadPart /= Int32x32To64(ONE_MINUTE_IN_TIME, 30);
|
|
|
|
Server->TimeZoneBias.QuadPart =
|
|
Workspace.QuadPart * Int32x32To64(ONE_MINUTE_IN_TIME, 30);
|
|
|
|
if ( Negated == TRUE ) {
|
|
Server->TimeZoneBias.QuadPart = -Server->TimeZoneBias.QuadPart;
|
|
}
|
|
|
|
} else if (Server->Capabilities & DF_NTNEGOTIATE) {
|
|
|
|
//
|
|
// Get our own timezone (in minutes from UCT)
|
|
//
|
|
|
|
USHORT LocalTimeZone = GetTimeZone();
|
|
|
|
LONG TimeZoneBiasInMinutes;
|
|
|
|
TimeZoneBiasInMinutes = (SHORT)Context.TimeZone - (SHORT)LocalTimeZone;
|
|
|
|
Server->TimeZoneBias.QuadPart =
|
|
Int32x32To64(TimeZoneBiasInMinutes, ONE_MINUTE_IN_TIME);
|
|
}
|
|
|
|
//
|
|
// Check to make sure that the time zone bias isn't more than +-24
|
|
// hours.
|
|
//
|
|
|
|
if ((Server->TimeZoneBias.QuadPart > RdrMaxTimezoneBias.QuadPart) ||
|
|
(-Server->TimeZoneBias.QuadPart > RdrMaxTimezoneBias.QuadPart)) {
|
|
|
|
//
|
|
// The time zone bias between this server and this workstation is
|
|
// too large. This is undoubtedly an error, so log it.
|
|
//
|
|
|
|
RdrWriteErrorLogEntry(Server,
|
|
IO_ERR_PROTOCOL,
|
|
EVENT_RDR_TIMEZONE_BIAS_TOO_LARGE,
|
|
STATUS_SUCCESS,
|
|
&Server->TimeZoneBias,
|
|
sizeof(Server->TimeZoneBias)
|
|
);
|
|
//
|
|
// Set the bias to 0 - assume local time zone.
|
|
//
|
|
|
|
Server->TimeZoneBias.QuadPart = 0;
|
|
}
|
|
|
|
dprintf(DPRT_CONNECT,(" TimeZoneBias:%x,%x\n",
|
|
Server->TimeZoneBias.HighPart,
|
|
Server->TimeZoneBias.LowPart ));
|
|
#if RDRDBG
|
|
if ( Server->TimeZoneBias.QuadPart < 0 ) {
|
|
dprintf(DPRT_CONNECT,(" TimeZoneBias (minutes): -%d\n",
|
|
(ULONG)(-Server->TimeZoneBias.QuadPart /
|
|
(ULONG) 10 * 1000 * 1000 * 60)));
|
|
} else {
|
|
dprintf(DPRT_CONNECT,(" TimeZoneBias (minutes): %d\n",
|
|
(ULONG)(Server->TimeZoneBias.QuadPart /
|
|
(ULONG) 10 * 1000 * 1000 * 60)));
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
Server->MaximumRequests = Context.MaximumRequests;
|
|
|
|
Server->MaximumVCs = Context.MaximumVCs;
|
|
|
|
Server->EncryptPasswords = Context.Encryption;
|
|
|
|
Server->SessionKey = Context.SessionKey;
|
|
|
|
if ( Context.Encryption == FALSE) {
|
|
Server->CryptKeyLength = 0;
|
|
} else {
|
|
Server->CryptKeyLength = Context.CryptKeyLength;
|
|
RtlCopyMemory(Server->CryptKey, Context.CryptKey, Context.CryptKeyLength);
|
|
}
|
|
Server->UserSecurity = Context.UserSecurity;
|
|
|
|
//
|
|
// Do not allow negotiated buffersize to exceed the size of a USHORT.
|
|
// Remove 4096 bytes to avoid overrun and make it easier to handle
|
|
// than 0xffff
|
|
//
|
|
|
|
Server->BufferSize =
|
|
(Context.BufferSize < (0x00010000 - 4096)) ? Context.BufferSize : 0x00010000 - 4096;
|
|
//if (SmallSmbs) {
|
|
// Server->BufferSize = 512;
|
|
//}
|
|
|
|
if (Context.SupportsLockRead) {
|
|
Server->Capabilities |= DF_LOCKREAD;
|
|
}
|
|
|
|
//
|
|
// We now know the maximum number of requests that can be issued
|
|
// to the server at one time.
|
|
//
|
|
|
|
if ((Server->Capabilities & DF_LANMAN10)==0) {
|
|
Server->MaximumRequests = 1;
|
|
}
|
|
|
|
//
|
|
// If we've a domain name, copy it into the server list entry
|
|
//
|
|
|
|
if(Context.DomainName.Buffer
|
|
&&
|
|
Context.DomainName.Buffer[0])
|
|
{
|
|
//
|
|
// yepper
|
|
//
|
|
|
|
if(Server->DomainName.Buffer)
|
|
{
|
|
FREE_POOL(Server->DomainName.Buffer);
|
|
Server->DomainName.Buffer = 0;
|
|
}
|
|
RdrpDuplicateUnicodeStringWithString(&Server->DomainName,
|
|
&Context.DomainName,
|
|
PagedPool,
|
|
FALSE);
|
|
|
|
}
|
|
|
|
#ifdef PAGING_OVER_THE_NET
|
|
//
|
|
// Re-initialize the maximum commands for this server appropriately.
|
|
//
|
|
|
|
if (Server->Flags & SLE_PAGING_FILE) {
|
|
|
|
//
|
|
// If this server has a paging file opened on it, then mark the max commands as
|
|
// -1, not what the server told us.
|
|
//
|
|
|
|
Status = RdrUpdateSmbExchangeForConnection(Se->TransportConnection, Se->TransportConnection->NumberOfEntries, 0xffffffff);
|
|
} else {
|
|
#endif
|
|
Status = RdrUpdateSmbExchangeForConnection(Server, 1, Server->MaximumRequests);
|
|
#ifdef PAGING_OVER_THE_NET
|
|
}
|
|
#endif
|
|
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
goto ReturnError;
|
|
}
|
|
|
|
if ((Server->Capabilities & DF_NTNEGOTIATE)!=0) {
|
|
|
|
RdrStatistics.LanmanNtConnects += 1;
|
|
|
|
} else if ((Server->Capabilities & DF_LANMAN21)!=0) {
|
|
|
|
RdrStatistics.Lanman21Connects += 1;
|
|
|
|
} else if ((Server->Capabilities & DF_LANMAN20)!=0) {
|
|
|
|
RdrStatistics.Lanman20Connects += 1;
|
|
|
|
} else {
|
|
|
|
RdrStatistics.CoreConnects += 1;
|
|
|
|
}
|
|
|
|
ReturnError:
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
//
|
|
// If this failed, hang up the VC with the server.
|
|
//
|
|
|
|
CleanupTransportConnection(Irp, NULL, Server);
|
|
|
|
}
|
|
|
|
//
|
|
// Release the MDL and buffer allocated to send the negotiate - we're
|
|
// done with them
|
|
//
|
|
|
|
if ( SMBBuffer != NULL ) {
|
|
RdrFreeSMBBuffer(SMBBuffer);
|
|
}
|
|
|
|
if(Context.DomainName.Buffer)
|
|
{
|
|
FREE_POOL(Context.DomainName.Buffer);
|
|
}
|
|
return Status;
|
|
|
|
}
|
|
|
|
DBGSTATIC
|
|
STANDARD_CALLBACK_HEADER (
|
|
NegotiateCallback
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called from the receive indication event handler to
|
|
handle the response to a negotiate SMB.
|
|
|
|
|
|
Arguments:
|
|
|
|
IN PSMB_HEADER Smb - SMB response from server.
|
|
IN PMPX_ENTRY MpxTable - MPX table entry for request.
|
|
IN PNEGOTIATECONTEXT Context- Context from caller.
|
|
IN BOOLEAN ErrorIndicator - TRUE if error indication
|
|
IN NTSTATUS NetworkErrorCode OPTIONAL - Network error if error indication.
|
|
IN OUT PIRP *Irp - IRP from TDI
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_PENDING if we are to complete the request
|
|
|
|
Notes:
|
|
MSNET 1.03 Servers only supply eight of the 13 bytes in an extended
|
|
response. They stop immediately after smb_sesskey leaving off the
|
|
time, date, timezone and cryptkey.
|
|
--*/
|
|
|
|
{
|
|
USHORT i;
|
|
PRESP_NEGOTIATE NegotiateResponse;
|
|
PRESP_NT_NEGOTIATE NtNegotiateResponse;
|
|
NTSTATUS Status;
|
|
|
|
PNEGOTIATECONTEXT Context = Ctx;
|
|
ASSERT(Context->Header.Type == CONTEXT_NEGOTIATE);
|
|
|
|
DISCARDABLE_CODE(RdrVCDiscardableSection);
|
|
|
|
UNREFERENCED_PARAMETER(MpxEntry);
|
|
UNREFERENCED_PARAMETER(Irp);
|
|
UNREFERENCED_PARAMETER(SmbLength);
|
|
UNREFERENCED_PARAMETER(Server);
|
|
|
|
Context->Header.ErrorType = NoError; // Assume no error at first.
|
|
|
|
//
|
|
// If we are called because the VC dropped, indicate it in the response
|
|
//
|
|
|
|
if (ErrorIndicator) {
|
|
Context->Header.ErrorType = NetError;
|
|
Context->Header.ErrorCode = RdrMapNetworkError(NetworkErrorCode);
|
|
KeSetEvent(&Context->Header.KernelEvent, IO_NETWORK_INCREMENT, FALSE);
|
|
return STATUS_SUCCESS; // Response ignored.
|
|
}
|
|
|
|
if (Smb->Command != SMB_COM_NEGOTIATE) {
|
|
|
|
//
|
|
// I didn't like this negotiate - ignore it.
|
|
//
|
|
|
|
InternalError(("Incorrect negotiate response"));
|
|
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
goto ReturnError;
|
|
}
|
|
|
|
NegotiateResponse = (PRESP_NEGOTIATE) (Smb+1);
|
|
|
|
NtNegotiateResponse = (PRESP_NT_NEGOTIATE) (Smb+1);
|
|
|
|
if (!NT_SUCCESS(Status = RdrMapSmbError(Smb,Server))) {
|
|
Context->Header.ErrorType = SMBError;
|
|
Context->Header.ErrorCode = Status;
|
|
goto ReturnError;
|
|
}
|
|
|
|
//
|
|
// Copy the information from the incoming SMB into the context
|
|
// structure.
|
|
//
|
|
|
|
Context->DialectIndex = SmbGetUshort(&NegotiateResponse->DialectIndex);
|
|
|
|
//
|
|
// If the dialect index is -1, this means that the server cannot
|
|
// accept any requests from this workstation.
|
|
//
|
|
|
|
if (Context->DialectIndex == (USHORT)-1) {
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_REQUEST_NOT_ACCEPTED;
|
|
|
|
goto ReturnError;
|
|
|
|
}
|
|
|
|
if ( NegotiateResponse->WordCount < 1 ||
|
|
Context->DialectIndex > RdrNumDialects ) {
|
|
|
|
//
|
|
// I didn't like this negotiate - ignore it.
|
|
//
|
|
|
|
InternalError(("Incorrect negotiate response"));
|
|
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
goto ReturnError;
|
|
}
|
|
|
|
Context->SupportsLockRead = (Smb->Flags & (UCHAR )0x01);
|
|
|
|
//
|
|
// If the guy on the other end is going to return extended info,
|
|
// copy that information.
|
|
//
|
|
|
|
if (RdrNegotiateDialect[Context->DialectIndex].DialectFlags&DF_NTNEGOTIATE) {
|
|
ULONG byteCount;
|
|
PWCHAR pwszDomainName;
|
|
|
|
if (NtNegotiateResponse->WordCount != 17) {
|
|
|
|
InternalError(("Incorrect WCT on NT negotiate response (got %ld, expected 17)", NtNegotiateResponse->WordCount));
|
|
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
goto ReturnError;
|
|
|
|
}
|
|
|
|
if ((NtNegotiateResponse->SecurityMode & 1) != 0) {
|
|
Context->UserSecurity = TRUE;
|
|
} else {
|
|
Context->UserSecurity = FALSE;
|
|
}
|
|
|
|
|
|
if ((NtNegotiateResponse->SecurityMode & 2) != 0) {
|
|
Context->Encryption = TRUE;
|
|
} else {
|
|
Context->Encryption = FALSE;
|
|
}
|
|
|
|
Context->MaximumRequests =
|
|
SmbGetUshort(&NtNegotiateResponse->MaxMpxCount);
|
|
Context->MaximumVCs = SmbGetUshort(&NtNegotiateResponse->MaxNumberVcs);
|
|
|
|
Context->BufferSize = SmbGetUlong(&NtNegotiateResponse->MaxBufferSize);
|
|
Context->MaxRawSize = SmbGetUlong(&NtNegotiateResponse->MaxRawSize);
|
|
Context->SessionKey = SmbGetUlong(&NtNegotiateResponse->SessionKey);
|
|
|
|
Context->Capabilities = SmbGetUlong(&NtNegotiateResponse->Capabilities);
|
|
|
|
Context->ServerTime.LowPart = SmbGetUlong(&NtNegotiateResponse->SystemTimeLow);
|
|
|
|
Context->ServerTime.HighPart = SmbGetUlong(&NtNegotiateResponse->SystemTimeHigh);
|
|
|
|
Context->TimeZone = SmbGetUshort(&NtNegotiateResponse->ServerTimeZone);
|
|
|
|
if (Context->Encryption == TRUE) {
|
|
PUSHORT ByteCount = ((PUSHORT)((PUCHAR)NtNegotiateResponse+1))+NtNegotiateResponse->WordCount;
|
|
PUCHAR Buffer = (PUCHAR)(ByteCount+1);
|
|
|
|
Context->CryptKeyLength = NtNegotiateResponse->EncryptionKeyLength;
|
|
|
|
if (Context->CryptKeyLength != 0) {
|
|
|
|
ASSERT (CRYPT_TXT_LEN == MSV1_0_CHALLENGE_LENGTH);
|
|
|
|
if (Context->CryptKeyLength != CRYPT_TXT_LEN) {
|
|
|
|
InternalError(("Illegal encryption key length in negotiateResponse %d\n",
|
|
Context->CryptKeyLength));
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
goto ReturnError;
|
|
}
|
|
|
|
RtlCopyMemory(Context->CryptKey, Buffer, Context->CryptKeyLength);
|
|
|
|
} else {
|
|
Context->Encryption = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy the domain name
|
|
//
|
|
|
|
|
|
byteCount = (ULONG)SmbGetUshort(&NtNegotiateResponse->ByteCount);
|
|
byteCount -= NtNegotiateResponse->EncryptionKeyLength;
|
|
|
|
//
|
|
// The following is to handle improperly aligned domain names
|
|
// from old servers. If the number of bytes remaining
|
|
// is odd, then the srv thought it needed to do some
|
|
// filling. So, we have to adjust for that!
|
|
//
|
|
|
|
pwszDomainName = (PWCHAR)(NtNegotiateResponse->Buffer +
|
|
NtNegotiateResponse->EncryptionKeyLength +
|
|
(byteCount & 1));
|
|
byteCount &= ~1;
|
|
|
|
if(byteCount
|
|
&&
|
|
Context->DomainName.Buffer
|
|
&&
|
|
(byteCount <= Context->DomainName.MaximumLength))
|
|
{
|
|
Context->DomainName.Length = (USHORT)(byteCount - sizeof(WCHAR));
|
|
RtlMoveMemory(Context->DomainName.Buffer,
|
|
pwszDomainName,
|
|
byteCount);
|
|
|
|
}
|
|
} else if (RdrNegotiateDialect[Context->DialectIndex].DialectFlags&DF_EXTENDNEGOT) {
|
|
|
|
if (NegotiateResponse->WordCount != 13 &&
|
|
NegotiateResponse->WordCount != 10 &&
|
|
NegotiateResponse->WordCount != 8) {
|
|
|
|
InternalError(("Incorrect WCT on negotiate response"));
|
|
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
goto ReturnError;
|
|
|
|
}
|
|
|
|
if ((SmbGetUshort(&NegotiateResponse->SecurityMode) & 1) != 0) {
|
|
Context->UserSecurity = TRUE;
|
|
} else {
|
|
Context->UserSecurity = FALSE;
|
|
}
|
|
|
|
|
|
if ((SmbGetUshort(&NegotiateResponse->SecurityMode) & 2) != 0) {
|
|
Context->Encryption = TRUE;
|
|
} else {
|
|
Context->Encryption = FALSE;
|
|
}
|
|
|
|
Context->BufferSize = SmbGetUshort(&NegotiateResponse->MaxBufferSize);
|
|
|
|
Context->MaximumRequests = SmbGetUshort(&NegotiateResponse->MaxMpxCount);
|
|
Context->MaximumVCs = SmbGetUshort(&NegotiateResponse->MaxNumberVcs);
|
|
|
|
Context->SessionKey = SmbGetUlong(&NegotiateResponse->SessionKey);
|
|
|
|
Context->SupportsRawWrite = Context->SupportsRawRead = FALSE;
|
|
|
|
if (SmbGetUshort(&NegotiateResponse->RawMode) & 0x01) {
|
|
Context->SupportsRawWrite = TRUE;
|
|
}
|
|
|
|
if (SmbGetUshort(&NegotiateResponse->RawMode) & 0x02) {
|
|
Context->SupportsRawRead = TRUE;
|
|
}
|
|
|
|
if (NegotiateResponse->WordCount == 13) {
|
|
SMB_TIME ServerTime;
|
|
SMB_DATE ServerDate;
|
|
|
|
SmbMoveTime(&ServerTime, &NegotiateResponse->ServerTime);
|
|
|
|
SmbMoveDate(&ServerDate, &NegotiateResponse->ServerDate);
|
|
|
|
Context->ServerTime = RdrConvertSmbTimeToTime(ServerTime, ServerDate, NULL);
|
|
|
|
Context->TimeZone = SmbGetUshort(&NegotiateResponse->ServerTimeZone);
|
|
|
|
if (Context->Encryption == TRUE) {
|
|
|
|
if (RdrNegotiateDialect[Context->DialectIndex].DialectFlags & DF_LANMAN21) {
|
|
Context->CryptKeyLength = SmbGetUshort(&NegotiateResponse->EncryptionKeyLength);
|
|
} else {
|
|
Context->CryptKeyLength = SmbGetUshort(&NegotiateResponse->ByteCount);
|
|
}
|
|
|
|
if (Context->CryptKeyLength != 0) {
|
|
|
|
if (Context->CryptKeyLength > CRYPT_TXT_LEN) {
|
|
InternalError(("Illegal encryption byte count in negotiateResponse %d\n",
|
|
Context->CryptKeyLength));
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
goto ReturnError;
|
|
#if DBG
|
|
} else if (Context->CryptKeyLength < CRYPT_TXT_LEN) {
|
|
InternalError(("Illegal encryption byte count in negotiateResponse %d\n",
|
|
Context->CryptKeyLength));
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
goto ReturnError;
|
|
#endif
|
|
}
|
|
|
|
for (i=0;i<Context->CryptKeyLength;i++) {
|
|
Context->CryptKey[i] = NegotiateResponse->Buffer[i];
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
|
|
if (Context->MaximumVCs == 0) {
|
|
Context->MaximumVCs = 1;
|
|
}
|
|
|
|
Context->Encryption = FALSE;
|
|
}
|
|
} else {
|
|
// Core level server
|
|
Context->UserSecurity = FALSE;
|
|
Context->Encryption = FALSE;
|
|
Context->BufferSize = 0;
|
|
Context->MaximumRequests = 1;
|
|
Context->MaximumVCs = 1;
|
|
Context->SessionKey = 0;
|
|
Context->SupportsRawWrite = Context->SupportsRawRead = FALSE;
|
|
}
|
|
|
|
if (Context->MaximumRequests == 0) {
|
|
|
|
//
|
|
// If this is a Lanman 1.0 or better server, we can't talk to this guy
|
|
//
|
|
// If he's MS-NET 1.03 (or core), we can, but we want to set
|
|
// maxrequests to 1.
|
|
//
|
|
|
|
if (FlagOn(RdrNegotiateDialect[Context->DialectIndex].DialectFlags, DF_LANMAN10) ) {
|
|
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
RdrStatistics.NetworkErrors += 1;
|
|
|
|
goto ReturnError;
|
|
|
|
} else {
|
|
|
|
Context->MaximumRequests = 1;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
ReturnError:
|
|
KeSetEvent(&Context->Header.KernelEvent, IO_NETWORK_INCREMENT, FALSE); // Wake up the caller
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
USHORT
|
|
GetTimeZone(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function gets the timezone bias in minutes fro UTC.
|
|
|
|
Arguments:
|
|
|
|
SystemTime - The current UTC time expressed.
|
|
|
|
Return Value:
|
|
|
|
The time zone bias in minutes from UTC.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
LARGE_INTEGER zeroTime;
|
|
union {
|
|
LARGE_INTEGER Signed;
|
|
ULARGE_INTEGER Unsigned;
|
|
} timeZoneBias;
|
|
SHORT biasInMinutes;
|
|
BOOLEAN biasIsNegative = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
zeroTime.LowPart = 0;
|
|
zeroTime.HighPart = 0;
|
|
|
|
//
|
|
// Specifying a zero local time will give you the time zone bias
|
|
//
|
|
|
|
ExLocalTimeToSystemTime(
|
|
&zeroTime,
|
|
&timeZoneBias.Signed
|
|
);
|
|
|
|
//
|
|
// RtlEnlargedUnsignedDivide operates on an unsigned large integer,
|
|
// so make the bias positive.
|
|
//
|
|
|
|
if ( timeZoneBias.Signed.QuadPart < 0 ) {
|
|
timeZoneBias.Signed.QuadPart = -timeZoneBias.Signed.QuadPart;
|
|
biasIsNegative = TRUE;
|
|
}
|
|
|
|
//
|
|
// Convert the bias unit from 100ns to minutes. The maximum value
|
|
// for the bias is 720 minutes so a USHORT is big enough to contain
|
|
// it.
|
|
//
|
|
|
|
biasInMinutes = (SHORT)( timeZoneBias.Unsigned.QuadPart / ONE_MINUTE_IN_TIME );
|
|
|
|
if ( biasIsNegative ) {
|
|
biasInMinutes = -biasInMinutes;
|
|
}
|
|
|
|
return biasInMinutes;
|
|
}
|
|
|
|
|
|
#ifdef _CAIRO_
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
SetupCairoSession (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds and exchanges a Cairo Session Setup as a Trans2
|
|
SMB with the remote server.
|
|
|
|
Arguments:
|
|
|
|
IN PCONNECTLISTENTRY Connection - Supplies the tree connection to connect.
|
|
IN PSECURITY_ENTRY Se - Security entry associated with connection.
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PSERVERLISTENTRY Server = Connection->Server;
|
|
CLONG ResponseLength, cResponseLength;
|
|
PRESP_CAIRO_TRANS2_SESSION_SETUP Response;
|
|
PVOID SessionSetupBuffer = NULL;
|
|
struct _TempSetup SetupData = { TRANS2_SESSION_SETUP,0,0,0 };
|
|
CLONG SetupDataCount = sizeof(SetupData);
|
|
|
|
PVOID InData;
|
|
CLONG InDataCount;
|
|
CLONG ZeroCount = 0;
|
|
PSECURITY_ENTRY Se1 = NULL;
|
|
ULONG ulBufferLength = 0;
|
|
PUCHAR pContextBuffer = NULL;
|
|
|
|
dprintf(DPRT_CAIRO, (" -- CreateCairoSessionSetup\n"));
|
|
|
|
ASSERT(ARGUMENT_PRESENT(Se));
|
|
|
|
ASSERT(Se->Signature == STRUCTURE_SIGNATURE_SECURITYENTRY);
|
|
|
|
//
|
|
// Attempt to get the kerberos blob and put it in a buffer
|
|
// to send to RdrTransact.
|
|
//
|
|
|
|
if (!NT_SUCCESS(Status = BuildCairoSessionSetup(Connection,
|
|
Se,
|
|
Server,
|
|
NULL,
|
|
0,
|
|
&InData,
|
|
&InDataCount,
|
|
&Response,
|
|
&ResponseLength)))
|
|
{
|
|
dprintf(DPRT_CAIRO, ("Build of Cairo SessionSetup failed\n"));
|
|
return Status;
|
|
}
|
|
|
|
ASSERT(InData); // It must have returned something.
|
|
|
|
//
|
|
// We may have to do this more than once. The one important case
|
|
// of this is receiving time-skew error from the srv and
|
|
// after correcting it, trying again. The loop goes on as long
|
|
// as Kerberos says to try.
|
|
|
|
|
|
do
|
|
{
|
|
//
|
|
// Now call rdrtransact with the blob in the input buffer.
|
|
//
|
|
//
|
|
|
|
dprintf(DPRT_CAIRO, (" -- CreateCairoSessionSetup- sending trans2\n"));
|
|
|
|
RdrReferenceDiscardableCode(RdrFileDiscardableSection);
|
|
|
|
Status = RdrTransact( Irp,
|
|
Connection,
|
|
Se,
|
|
&SetupData, // IN OUT PVOID Setup,
|
|
SetupDataCount, // IN CLONG InSetupCount,
|
|
&SetupDataCount, // IN OUT PCLONG OutSetupCount,
|
|
NULL, // IN PUNICODE_STRING Name OPTIONAL,
|
|
NULL, // IN OUT PVOID Parameters,
|
|
0, // IN CLONG InParameterCount,
|
|
&ZeroCount, // IN OUT PCLONG OutParameterCount,
|
|
InData, // IN PVOID InData OPTIONAL,
|
|
InDataCount, // IN CLONG InDataCount,
|
|
Response, // OUT PVOID OutData OPTIONAL,
|
|
&ResponseLength, // IN OUT PCLONG OutDataCount,
|
|
NULL, // IN PUSHORT Fid OPTIONAL,
|
|
0, // IN ULONG TimeoutInMilliseconds,
|
|
SMB_TRANSACTION_RECONNECTING, // IN USHORT Flags,
|
|
0, // IN USHORT NtTransactFunction
|
|
NULL, // IN CompletionRoutine
|
|
NULL // IN CallbackContext
|
|
);
|
|
|
|
|
|
RdrDereferenceDiscardableCode(RdrFileDiscardableSection);
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
dprintf(0, ("Send of trans2 failed: %lx\n", Status));
|
|
return Status;
|
|
}
|
|
|
|
dprintf(DPRT_CAIRO, (" -- CreateCairoSessionSetup- sent trans2\n"));
|
|
|
|
ExInterlockedAddUlong( &RdrStatistics.Sessions, 1, &RdrStatisticsSpinLock );
|
|
|
|
//
|
|
// Update the UID with the new UID from the session setup.
|
|
//
|
|
|
|
|
|
Se->UserId = Response->Uid;
|
|
|
|
RdrLog(("scs find",NULL,4,PsGetCurrentThread(),Server,Se->LogonId.LowPart,Se->LogonId.HighPart));
|
|
Se1 = RdrFindActiveSecurityEntry(Server, &Se->LogonId);
|
|
|
|
|
|
if ( (Se1 == NULL) || ( Se1 != Se ) )
|
|
{
|
|
|
|
//
|
|
// Need to send the LSA the returned Blob, if any.
|
|
//
|
|
|
|
if ( Se->Flags & SE_BLOB_NEEDS_VERIFYING )
|
|
{
|
|
|
|
PRESP_CAIRO_TRANS2_SESSION_SETUP Response2 = 0;
|
|
|
|
//
|
|
// BUGBUG Due to the unfortunate misalignment of the PRESP_...
|
|
// struct, we have to declare these stack based variables, and
|
|
// then copy them into the struct.
|
|
//
|
|
// We should probably change the PRESP_... struct so it is
|
|
// properly aligned. We are currently not doing so because we
|
|
// need a cairo server (DC) to talk to bootstrap the MIPS build.
|
|
//
|
|
|
|
|
|
// We are doing Kerberos authentication. The returned Blob is in the
|
|
// Context block. Feed it back to the LSA
|
|
//
|
|
|
|
Se->Flags &= ~SE_BLOB_NEEDS_VERIFYING;
|
|
|
|
Status = BuildCairoSessionSetup(Connection,
|
|
Se,
|
|
Server,
|
|
Response->Buffer,
|
|
Response->BufferLength,
|
|
&pContextBuffer,
|
|
&ulBufferLength,
|
|
&Response2,
|
|
&ResponseLength);
|
|
|
|
ExFreePool(Response); // get rid of this
|
|
|
|
if (!NT_SUCCESS(Status) && !NT_INFORMATION(Status)) {
|
|
dprintf(DPRT_CAIRO, (" -- CreateCairoSessionSetup- get kerb blob failed, status = %lC\n",Status));
|
|
return Status;
|
|
}
|
|
|
|
if(pContextBuffer && Response2)
|
|
{
|
|
//
|
|
// We have to go again. So copy pointers and
|
|
// redo the Transact operation. Note that this
|
|
// time we send only the blob and not the other
|
|
// stuff.
|
|
//
|
|
|
|
InData = pContextBuffer;
|
|
InDataCount = ulBufferLength;
|
|
Response = Response2;
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if(Response2)
|
|
{
|
|
ExFreePool(Response2);
|
|
}
|
|
if(pContextBuffer)
|
|
{
|
|
ExFreePool(pContextBuffer);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
PVOID caller,callerscaller;
|
|
RtlGetCallersAddress(&caller,&callerscaller);
|
|
RdrLog(("scs ise",NULL,5,PsGetCurrentThread(),Server,Se,caller,callerscaller));
|
|
}
|
|
RdrInsertSecurityEntryList(Server, Se);
|
|
|
|
dprintf(DPRT_CAIRO, ("CreateCairoSessionSetup - Linking Se %lx to Server %lx\n", Se, Server));
|
|
|
|
Se->Server = Server;
|
|
break;
|
|
}
|
|
}while(TRUE);
|
|
|
|
if (Se1 != NULL)
|
|
{
|
|
|
|
//
|
|
// If we found a security entry, dereference it.
|
|
//
|
|
|
|
dprintf(DPRT_CAIRO, ("CreateCairoSessionSetup - Derefing Se1 (found) %lx current = %lx\n", Se1, Se));
|
|
RdrDereferenceSecurityEntry(Se1->NonPagedSecurityEntry);
|
|
|
|
}
|
|
|
|
dprintf(DPRT_CAIRO, (" -- CreateCairoSessionSetup- done, status = %lC\n",Status));
|
|
return Status;
|
|
}
|
|
|
|
#endif // _CAIRO_
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
CreateTreeConnection (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN ULONG Type,
|
|
IN PSECURITY_ENTRY Se
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds and exchanges a TreeConnect SMB with the remote
|
|
server.
|
|
|
|
Arguments:
|
|
|
|
IN PCONNECTLISTENTRY Connection - Supplies the tree connection to connect.
|
|
IN ULONG Type - Type of connection.
|
|
IN PSECURITY_ENTRY Se - Security entry associated with connection.
|
|
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PSERVERLISTENTRY Server = Connection->Server;
|
|
PSMB_BUFFER Smb = NULL;
|
|
BOOLEAN ServerNeedsSession = FALSE;
|
|
BOOLEAN ConnectionNeedsTreeConnect = FALSE;
|
|
BOOLEAN ConnectionObjectReferenced = FALSE;
|
|
TREECONNECTCONTEXT Context;
|
|
PVOID SessionSetupBuffer = NULL;
|
|
BOOLEAN SessionSetupBufferLocked = FALSE;
|
|
|
|
PAGED_CODE();
|
|
|
|
InterlockedIncrement( &RdrServerStateUpdated );
|
|
|
|
Context.ReceiveIrp = NULL;
|
|
Context.SessionSetupMdl = NULL;
|
|
|
|
try {
|
|
|
|
Context.Header.Type = CONTEXT_TREECONNECT;
|
|
|
|
Context.SessionSetupMdl = NULL;
|
|
|
|
Context.ReceiveIrp = NULL;
|
|
|
|
Context.BufferTooShort = FALSE;
|
|
|
|
Context.UseLmSessionSetupKey = FALSE;
|
|
|
|
Context.ShareIsInDfs = FALSE;
|
|
|
|
ASSERT(Se->Signature == STRUCTURE_SIGNATURE_SECURITYENTRY);
|
|
|
|
if (Server->Capabilities & DF_LANMAN10) {
|
|
if (!NT_SUCCESS(Status = BuildLanmanConnect(Connection,
|
|
&Smb,
|
|
Type,
|
|
Se,
|
|
&ServerNeedsSession,
|
|
&ConnectionNeedsTreeConnect))) {
|
|
dprintf(DPRT_CONNECT, ("Build of LANMAN Connect failed\n"));
|
|
|
|
try_return(Status);
|
|
}
|
|
} else {
|
|
|
|
if ( Type == CONNECT_IPC ) {
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
|
|
try_return(Status);
|
|
}
|
|
|
|
if ( Type == CONNECT_WILD ) {
|
|
NTSTATUS Status1;
|
|
|
|
//
|
|
// Allow either disk or lpt. Try with disk using recursion.
|
|
// If that fails, try LPT, but return the error for disk,
|
|
// since it's more likely to be a reasonable error.
|
|
//
|
|
|
|
if (NT_SUCCESS(Status = CreateTreeConnection (Irp, Connection,
|
|
CONNECT_DISK,
|
|
Se))) {
|
|
dprintf(DPRT_CONNECT, ("Build of TreeConnect (Disk) success\n"));
|
|
|
|
try_return(Status);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status1 = CreateTreeConnection (Irp, Connection,
|
|
CONNECT_PRINT,
|
|
Se))) {
|
|
dprintf(DPRT_CONNECT, ("Build of TreeConnect (Print) success\n"));
|
|
|
|
try_return(Status1);
|
|
}
|
|
|
|
//
|
|
// To allow the Net command to handle case mapping properly,
|
|
// we want to change STATUS_WRONG_PASSWORD into
|
|
// STATUS_WRONG_PASSWORD_CORE.
|
|
//
|
|
|
|
if (Status == STATUS_WRONG_PASSWORD) {
|
|
Status = STATUS_WRONG_PASSWORD_CORE;
|
|
}
|
|
|
|
try_return(Status);
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status = BuildCoreConnect(Connection, &Smb,
|
|
Type, Se))) {
|
|
dprintf(DPRT_CONNECT, ("Build of TreeConnect failed\n"));
|
|
|
|
try_return(Status);
|
|
}
|
|
|
|
Context.Type = Type;
|
|
|
|
ConnectionNeedsTreeConnect = TRUE;
|
|
|
|
}
|
|
|
|
ASSERT (Se->Signature == STRUCTURE_SIGNATURE_SECURITYENTRY);
|
|
|
|
ASSERT (ConnectionNeedsTreeConnect | ServerNeedsSession);
|
|
|
|
//
|
|
// The status returned from RdrNetTranceiveWithCallback in this case
|
|
// will only return an error if the network operation failed, not
|
|
// in the case that the SMB being exchanged failed. Those errors
|
|
// are mapped in the context block.
|
|
//
|
|
|
|
if (Server->Capabilities & DF_LANMAN21) {
|
|
|
|
//
|
|
// If this is a LANMAN 2.1 server, then the response to the
|
|
// sessionsetup/treeconnect contains "interesting stuff".
|
|
//
|
|
// Unfortunately, it is possible that this "interesting stuff"
|
|
// might overflow the 128 byte minimum indication size we are
|
|
// guaranteed by TDI. Thus we have to turn this into a "large I/O"
|
|
// API. Sigh....
|
|
//
|
|
|
|
SessionSetupBuffer = ALLOCATE_POOL(PagedPool, Server->BufferSize, POOL_SESSIONSETUPBUFFER);
|
|
|
|
if (SessionSetupBuffer == NULL) {
|
|
try_return(Status = STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
Context.SessionSetupMdl = IoAllocateMdl(SessionSetupBuffer,
|
|
Server->BufferSize,
|
|
FALSE, FALSE, NULL);
|
|
if (Context.SessionSetupMdl == NULL) {
|
|
try_return(Status = STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
try {
|
|
|
|
MmProbeAndLockPages(Context.SessionSetupMdl, KernelMode, IoWriteAccess);
|
|
|
|
} except (EXCEPTION_EXECUTE_HANDLER) {
|
|
try_return(Status = GetExceptionCode());
|
|
}
|
|
SessionSetupBufferLocked = TRUE;
|
|
|
|
|
|
KeInitializeEvent(&Context.ReceiveCompleteEvent, NotificationEvent, TRUE);
|
|
|
|
//
|
|
// We are about to reference the transport connection. Make
|
|
// sure that we can safely do this.
|
|
//
|
|
|
|
Status = RdrReferenceTransportConnection(Server);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
ConnectionObjectReferenced = TRUE;
|
|
|
|
Context.ReceiveIrp = ALLOCATE_IRP(
|
|
Server->ConnectionContext->ConnectionObject,
|
|
NULL,
|
|
1,
|
|
&Context
|
|
);
|
|
|
|
if (Context.ReceiveIrp == NULL) {
|
|
try_return(Status = STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
RdrBuildReceive(Context.ReceiveIrp, Server,
|
|
TreeConnectComplete, &Context,
|
|
Context.SessionSetupMdl, RdrMdlLength(Context.SessionSetupMdl));
|
|
|
|
//
|
|
// This gets kinda wierd.
|
|
//
|
|
// Since this IRP is going to be completed by the transport without
|
|
// ever going to IoCallDriver, we have to update the stack location
|
|
// to make the transports stack location the current stack location.
|
|
//
|
|
// Please note that this means that any transport provider that uses
|
|
// IoCallDriver to re-submit it's requests at indication time will
|
|
// break badly because of this code....
|
|
//
|
|
|
|
IoSetNextIrpStackLocation( Context.ReceiveIrp );
|
|
}
|
|
|
|
Context.Header.TransferSize = Smb->Mdl->ByteCount + Server->BufferSize;
|
|
|
|
Status = RdrNetTranceiveWithCallback(
|
|
NT_RECONNECTING | NT_DONTSCROUNGE | NT_NOCONNECTLIST | NT_CANNOTCANCEL,
|
|
Irp,// Irp
|
|
Connection, // Server to exchange SMB's on
|
|
Smb->Mdl, // Smb to send
|
|
&Context,
|
|
(Server->Capabilities & DF_LANMAN10) ?
|
|
TreeConnectCallback : CoreTreeConnectCallback,
|
|
Se,
|
|
NULL);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
dprintf(DPRT_CONNECT, ("Send of treeconnect failed: %X\n", Status));
|
|
try_return(Status);
|
|
}
|
|
|
|
if (Context.SessionSetupMdl != NULL) {
|
|
//
|
|
// We don't need to have the user's buffer locked any more, release it.
|
|
//
|
|
|
|
ASSERT(SessionSetupBufferLocked);
|
|
MmUnlockPages(Context.SessionSetupMdl);
|
|
SessionSetupBufferLocked = FALSE;
|
|
}
|
|
|
|
if (Context.BufferTooShort) {
|
|
PSMB_HEADER Smb = SessionSetupBuffer;
|
|
PRESP_TREE_CONNECT_ANDX TreeConnect;
|
|
PRESP_21_TREE_CONNECT_ANDX LM21TreeConnect;
|
|
PVOID p;
|
|
PGENERIC_ANDX AndX = (PGENERIC_ANDX )(Smb+1);
|
|
|
|
if (Smb->Command == SMB_COM_SESSION_SETUP_ANDX) {
|
|
|
|
TreeConnect = (PRESP_TREE_CONNECT_ANDX)((PCHAR )Smb+
|
|
SmbGetUshort(&AndX->AndXOffset));
|
|
|
|
LM21TreeConnect = (PRESP_21_TREE_CONNECT_ANDX) TreeConnect;
|
|
} else {
|
|
|
|
TreeConnect = (PRESP_TREE_CONNECT_ANDX )(Smb+1);
|
|
|
|
LM21TreeConnect = (PRESP_21_TREE_CONNECT_ANDX) TreeConnect;
|
|
|
|
}
|
|
|
|
if (((Smb->Command == SMB_COM_SESSION_SETUP_ANDX) &&
|
|
(AndX->AndXCommand == SMB_COM_TREE_CONNECT_ANDX)) ||
|
|
|
|
(Smb->Command == SMB_COM_TREE_CONNECT_ANDX)) {
|
|
|
|
//
|
|
// If the whole contents of the response couldn't fit in the indicated
|
|
// data, we need to re-parse the response SMB.
|
|
//
|
|
|
|
if (TreeConnect->WordCount == 2) {
|
|
p = (PVOID)TreeConnect->Buffer;
|
|
} else if (TreeConnect->WordCount == 3) {
|
|
p = (PVOID)LM21TreeConnect->Buffer;
|
|
} else {
|
|
try_return(Status = STATUS_UNEXPECTED_NETWORK_ERROR);
|
|
}
|
|
|
|
Status = ProcessTreeConnectAndXBuffer(Smb, p, &Context, Server, Context.ReceiveLength - ((PCHAR)p-(PCHAR)Smb), TDI_RECEIVE_COPY_LOOKAHEAD);
|
|
|
|
} else {
|
|
|
|
//
|
|
// We can only deal with SessionSetup&TreeConnect or
|
|
// TreeConnect&X commands here. Any other command indicates
|
|
// that the server sent us a bogus response.
|
|
//
|
|
|
|
Status = STATUS_UNEXPECTED_NETWORK_ERROR;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
}
|
|
|
|
ASSERT(Context.Header.ErrorType==NoError);
|
|
|
|
if (ServerNeedsSession) {
|
|
|
|
if (NT_SUCCESS(Context.SessionSetupError)) {
|
|
|
|
BOOLEAN EntryFound = FALSE;
|
|
|
|
RdrStatistics.Sessions += 1;
|
|
|
|
if (Context.UseLmSessionSetupKey) {
|
|
|
|
//
|
|
// If we're using the LM session setup key, we need to
|
|
// copy over the LanmanSessionKey on top of the
|
|
// UserSessionKey
|
|
//
|
|
|
|
RtlZeroMemory(Se->UserSessionKey, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
RtlCopyMemory(Se->UserSessionKey, Se->LanmanSessionKey, MSV1_0_LANMAN_SESSION_KEY_LENGTH);
|
|
|
|
}
|
|
|
|
//
|
|
// Update the UID with the new UID from the session setup.
|
|
//
|
|
|
|
Se->UserId = Context.UserId;
|
|
|
|
ASSERT (RdrFindActiveSecurityEntry(Server,
|
|
&Se->LogonId) == NULL);
|
|
|
|
//
|
|
// We're done - link this security entry to the server.
|
|
//
|
|
|
|
{
|
|
PVOID caller,callerscaller;
|
|
RtlGetCallersAddress(&caller,&callerscaller);
|
|
RdrLog(("ctc ise1",NULL,5,PsGetCurrentThread(),Server,Se,caller,callerscaller));
|
|
}
|
|
RdrInsertSecurityEntryList(Server, Se);
|
|
|
|
if (!Server->UserSecurity) {
|
|
PLIST_ENTRY SeEntry;
|
|
PSECURITY_ENTRY OtherSecurityEntry;
|
|
|
|
LOCK_SECURITY_DATABASE();
|
|
|
|
//
|
|
// If this is a share level security server, we want to
|
|
// track down any other potential security entries
|
|
// for this user and mark them as being active as
|
|
// well.
|
|
//
|
|
|
|
|
|
for (SeEntry = Server->PotentialSecurityList.Flink ;
|
|
SeEntry != &Server->PotentialSecurityList ;
|
|
SeEntry = SeEntry->Flink) {
|
|
OtherSecurityEntry = CONTAINING_RECORD(SeEntry, SECURITY_ENTRY, PotentialNext);
|
|
|
|
//
|
|
// If this security entry isn't active, and
|
|
// it's for this user, then we want to copy over
|
|
// the userid to this security entry.
|
|
//
|
|
|
|
ASSERT (OtherSecurityEntry->Signature == STRUCTURE_SIGNATURE_SECURITYENTRY);
|
|
ASSERT (OtherSecurityEntry->NonPagedSecurityEntry->Signature == STRUCTURE_SIGNATURE_NONPAGED_SECURITYENTRY);
|
|
ASSERT (OtherSecurityEntry->NonPagedSecurityEntry->PagedSecurityEntry == OtherSecurityEntry);
|
|
|
|
if (OtherSecurityEntry->ActiveNext.Flink == NULL &&
|
|
RtlEqualLuid(&OtherSecurityEntry->LogonId, &Se->LogonId)) {
|
|
|
|
ASSERT (OtherSecurityEntry->ActiveNext.Blink == NULL);
|
|
|
|
OtherSecurityEntry->UserId = Context.UserId;
|
|
|
|
{
|
|
PVOID caller,callerscaller;
|
|
RtlGetCallersAddress(&caller,&callerscaller);
|
|
RdrLog(("ctc ise2",NULL,5,PsGetCurrentThread(),Server,Se,caller,callerscaller));
|
|
}
|
|
RdrInsertSecurityEntryList(Server, OtherSecurityEntry);
|
|
}
|
|
}
|
|
|
|
UNLOCK_SECURITY_DATABASE();
|
|
|
|
}
|
|
|
|
dprintf(DPRT_SECURITY, ("Linking Se %lx to Server %lx\n", Se, Server));
|
|
|
|
Se->Server = Server;
|
|
|
|
} else {
|
|
RdrStatistics.FailedSessions += 1;
|
|
|
|
// //
|
|
// // This security entry isn't a potential security entry
|
|
// // any more.
|
|
// //
|
|
//
|
|
//
|
|
// RdrRemovePotentialSecurityEntry(Se);
|
|
//
|
|
ASSERT (RdrFindActiveSecurityEntry(Server,
|
|
&Se->LogonId) == NULL);
|
|
|
|
Status = Context.SessionSetupError;
|
|
|
|
try_return(Status);
|
|
}
|
|
}
|
|
|
|
if (ConnectionNeedsTreeConnect) {
|
|
if (NT_SUCCESS(Context.TreeConnectError)) {
|
|
|
|
Connection->Type = Context.Type;
|
|
|
|
Connection->TreeId = Context.TreeId;
|
|
|
|
Connection->HasTreeId = TRUE;
|
|
|
|
//
|
|
// This connection is no longer deleted.
|
|
//
|
|
|
|
Connection->Deleted = FALSE;
|
|
|
|
if (!(Server->Capabilities & DF_LANMAN10)) {
|
|
|
|
//
|
|
// Buffersize is in tcon instead of the negotiate for core SMB
|
|
// so Server_>Buffersize is set here. Some downlevel servers
|
|
// negotiate a larger buffersize but if you use it they crash.
|
|
// hence the heuristic.
|
|
//
|
|
|
|
Server->BufferSize = ( RdrData.Use512ByteMaximumTransfer) ?
|
|
(USHORT)512 : Context.BufferSize;
|
|
|
|
//
|
|
// Mark this as an active security entry for the server.
|
|
//
|
|
|
|
if (Se->ActiveNext.Flink == NULL) {
|
|
|
|
{
|
|
PVOID caller,callerscaller;
|
|
RtlGetCallersAddress(&caller,&callerscaller);
|
|
RdrLog(("ctc ise3",NULL,5,PsGetCurrentThread(),Server,Se,caller,callerscaller));
|
|
}
|
|
RdrInsertSecurityEntryList(Server, Se);
|
|
}
|
|
|
|
} else if (Server->Capabilities & DF_LANMAN21) {
|
|
if (Server->Capabilities & DF_UNICODE) {
|
|
ULONG fslen;
|
|
|
|
wcsncpy(Connection->FileSystemType, Context.FileSystemType, LM20_DEVLEN);
|
|
|
|
fslen = wcslen(Context.FileSystemType);
|
|
|
|
Connection->FileSystemTypeLength = (USHORT)(MIN(fslen, LM20_DEVLEN)*sizeof(WCHAR));
|
|
} else {
|
|
UNICODE_STRING FsType;
|
|
OEM_STRING FsTypeA;
|
|
|
|
FsType.Buffer = Connection->FileSystemType;
|
|
FsType.MaximumLength = (LM20_DEVLEN+1)*sizeof(WCHAR);
|
|
|
|
RtlInitAnsiString(&FsTypeA, (LPSTR)Context.FileSystemType);
|
|
|
|
Status = RtlOemStringToUnicodeString(&FsType, &FsTypeA, FALSE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
Connection->FileSystemTypeLength = FsType.Length;
|
|
}
|
|
|
|
if (Context.ShareIsInDfs) {
|
|
Connection->Flags |= CLE_IS_A_DFS_SHARE;
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// If we just created a session to the server, then we need to
|
|
// clean up from that operation before we return the error.
|
|
//
|
|
// The Security entry has been linked into the connection
|
|
// already, so we just want to unlink it and log the user id off.
|
|
//
|
|
|
|
if (ServerNeedsSession) {
|
|
RdrUserLogoff(Irp, Connection, Se);
|
|
}
|
|
|
|
Status = Context.TreeConnectError;
|
|
|
|
try_return(Status);
|
|
}
|
|
}
|
|
try_exit:NOTHING;
|
|
} finally {
|
|
if (Smb != NULL) {
|
|
RdrFreeSMBBuffer(Smb);
|
|
}
|
|
|
|
if (Context.ReceiveIrp != NULL) {
|
|
NTSTATUS Status1;
|
|
|
|
Status1 = KeWaitForSingleObject(&Context.ReceiveCompleteEvent,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
|
|
FREE_IRP( Context.ReceiveIrp, 1, &Context );
|
|
|
|
}
|
|
|
|
if (ConnectionObjectReferenced) {
|
|
RdrDereferenceTransportConnection(Server);
|
|
}
|
|
|
|
if (Context.SessionSetupMdl != NULL) {
|
|
|
|
if (SessionSetupBufferLocked) {
|
|
MmUnlockPages(Context.SessionSetupMdl);
|
|
}
|
|
|
|
IoFreeMdl(Context.SessionSetupMdl);
|
|
|
|
} else {
|
|
ASSERT(!SessionSetupBufferLocked);
|
|
}
|
|
|
|
if (SessionSetupBuffer != NULL) {
|
|
FREE_POOL(SessionSetupBuffer);
|
|
}
|
|
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildLanmanConnect (
|
|
IN PCONNECTLISTENTRY Connection,
|
|
OUT PSMB_BUFFER *SmbBuffer,
|
|
IN ULONG Type,
|
|
IN PSECURITY_ENTRY Se,
|
|
OUT PBOOLEAN ServerNeedsSession,
|
|
OUT PBOOLEAN ConnectionNeedsTreeConnect
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds a sessionsetup&treeconnect&x SMB to establish the
|
|
specified connection to the remote server.
|
|
|
|
Arguments:
|
|
|
|
IN PCONNECTLISTENTRY Connection - Supplies the connection to connect to.
|
|
OUT PSMB_BUFFER *SmbBuffer - Returns a filled in SMB buffer.
|
|
IN ULONG Type - Type of connection (WILD, Printer, COMM, etc)
|
|
OUT PBOOLEAN ServerNeedsSession - True iff SessionSetup&TreeConnect SMB.
|
|
OUT PSECURITY_ENTRY Se - Security entry if session needed.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of operation
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PSMB_HEADER SmbHeader;
|
|
PSECURITY_ENTRY Se1 = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
if ((*SmbBuffer = RdrAllocateSMBBuffer())==NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
SmbHeader = (PSMB_HEADER )(*SmbBuffer)->Buffer;
|
|
|
|
RdrSmbScrounge(SmbHeader, Connection->Server, FALSE, TRUE, TRUE);
|
|
|
|
//
|
|
// If either there is no session to the server with this security entry,
|
|
// or the security entry is not valid, re-send the session setup&x.
|
|
//
|
|
|
|
if (!FlagOn(Se->Flags, SE_HAS_SESSION)) {
|
|
|
|
ASSERT (RdrFindActiveSecurityEntry(Connection->Server,
|
|
&Se->LogonId) == NULL);
|
|
|
|
*ServerNeedsSession = TRUE;
|
|
|
|
//
|
|
// Builds a session setup&X SMB for this security entry for this
|
|
// server.
|
|
//
|
|
|
|
Status = BuildSessionSetupAndX(*SmbBuffer, Connection, Se);
|
|
|
|
//
|
|
// If we were able to build the session setup&x, and the connection
|
|
// doesn't have a valid tree ID, combine a tree connection on top of
|
|
// the session setup&xv SMB.
|
|
//
|
|
|
|
|
|
if (NT_SUCCESS(Status) && !(Connection->HasTreeId) ) {
|
|
|
|
*ConnectionNeedsTreeConnect = TRUE;
|
|
|
|
Status = BuildTreeConnectAndX(*SmbBuffer,Connection, Se, TRUE, Type);
|
|
|
|
}
|
|
} else {
|
|
|
|
*ServerNeedsSession = FALSE;
|
|
|
|
*ConnectionNeedsTreeConnect = TRUE;
|
|
|
|
Status = BuildTreeConnectAndX(*SmbBuffer,Connection, Se,FALSE, Type);
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
RdrFreeSMBBuffer(*SmbBuffer);
|
|
|
|
*SmbBuffer = NULL;
|
|
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildCoreConnect (
|
|
IN PCONNECTLISTENTRY Connection,
|
|
OUT PSMB_BUFFER *SmbBuffer,
|
|
IN ULONG Type,
|
|
IN PSECURITY_ENTRY Se
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds a Treeconnect SMB to establish the
|
|
specified connection to the remote server.
|
|
|
|
Arguments:
|
|
|
|
IN PCONNECTLISTENTRY Connection - Supplies the connection to connect to.
|
|
OUT PSMB_BUFFER *SmbBuffer - Returns a filled in SMB buffer.
|
|
IN ULONG Type - Type of connection (WILD, Printer, COMM, etc)
|
|
IN PSECURITY_ENTRY Se - Security entry for connection if new.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Status of operation
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PSMB_HEADER SmbHeader;
|
|
PREQ_TREE_CONNECT TreeConnect;
|
|
PVOID p;
|
|
|
|
PAGED_CODE();
|
|
|
|
if ((*SmbBuffer = RdrAllocateSMBBuffer())==NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
SmbHeader = (PSMB_HEADER )(*SmbBuffer)->Buffer;
|
|
RdrSmbScrounge(SmbHeader, Connection->Server, FALSE, FALSE, FALSE);
|
|
SmbHeader->Command = SMB_COM_TREE_CONNECT;
|
|
|
|
TreeConnect = (PREQ_TREE_CONNECT )(SmbHeader+1);
|
|
TreeConnect->WordCount = 0;
|
|
|
|
p = (PVOID)TreeConnect->Buffer;
|
|
|
|
// path such as \\lothair\d
|
|
*((PCHAR)p)++ = SMB_FORMAT_ASCII;
|
|
|
|
Status = RdrCanonicalizeAndCopyShare(&p, &Connection->Server->Text, &Connection->Text, Connection->Server);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
RdrFreeSMBBuffer(*SmbBuffer);
|
|
*SmbBuffer = NULL;
|
|
return Status;
|
|
}
|
|
|
|
// Password
|
|
*((PCHAR)p)++ = SMB_FORMAT_ASCII;
|
|
|
|
if (!(Se->Flags & SE_USE_DEFAULT_PASS)) {
|
|
OEM_STRING DestinationString;
|
|
UNICODE_STRING Password;
|
|
|
|
DestinationString.Buffer = (PUCHAR)p;
|
|
|
|
DestinationString.MaximumLength = LM20_PWLEN+1;
|
|
|
|
DestinationString.Length = 0;
|
|
|
|
if (Se->Password.MaximumLength != 0) {
|
|
Password.Buffer = ALLOCATE_POOL(PagedPool, Se->Password.MaximumLength, POOL_PASSWORD);
|
|
} else {
|
|
Password.Buffer = NULL;
|
|
}
|
|
|
|
Password.MaximumLength = Se->Password.MaximumLength;
|
|
|
|
//
|
|
// If the server doesn't support mixed case passwords,
|
|
// upper case the password before connecting.
|
|
//
|
|
|
|
if (!(Connection->Server->Capabilities & DF_MIXEDCASEPW)) {
|
|
RtlUpcaseUnicodeString(&Password, &Se->Password, FALSE);
|
|
} else {
|
|
RtlCopyUnicodeString(&Password, &Se->Password);
|
|
}
|
|
|
|
Status = RtlUnicodeStringToOemString(&DestinationString, &Password, FALSE);
|
|
|
|
if (Password.Buffer != NULL) {
|
|
FREE_POOL(Password.Buffer);
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
(PUCHAR)p+=DestinationString.Length;
|
|
|
|
|
|
}
|
|
*((PCHAR)p)++ = '\0';
|
|
|
|
// Device Name
|
|
*((PCHAR)p)++ = SMB_FORMAT_ASCII;
|
|
strcpy(p, RdrConnectTypes[Type]);
|
|
(PCHAR)p += strlen(RdrConnectTypes[Type]);
|
|
*((PCHAR)p)++ = '\0';
|
|
|
|
SmbPutUshort(&TreeConnect->ByteCount, (USHORT )((PCHAR)p-TreeConnect->Buffer));
|
|
(*SmbBuffer)->Mdl->ByteCount = (USHORT )((PCHAR)p-(PUCHAR)SmbHeader);
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
DBGSTATIC
|
|
STANDARD_CALLBACK_HEADER (
|
|
CoreTreeConnectCallback
|
|
)
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called from the receive indication event handler to
|
|
handle the response to a treeconnect SMB.
|
|
|
|
|
|
Arguments:
|
|
|
|
IN PSMB_HEADER Smb - SMB response from server.
|
|
IN PMPX_ENTRY MpxEntry - MPX table entry for request.
|
|
IN PVOID Context - Context from caller.
|
|
IN BOOLEAN ErrorIndicator - TRUE if error indication
|
|
IN NTSTATUS NetworkErrorCode OPTIONAL - Network error if error indication.
|
|
IN OUT PIRP *Irp - IRP from TDI
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS
|
|
|
|
--*/
|
|
{
|
|
PRESP_TREE_CONNECT TreeConnect;
|
|
PTREECONNECTCONTEXT Context = Ctx;
|
|
NTSTATUS Status;
|
|
|
|
DISCARDABLE_CODE(RdrVCDiscardableSection);
|
|
|
|
UNREFERENCED_PARAMETER(MpxEntry);
|
|
UNREFERENCED_PARAMETER(Irp);
|
|
UNREFERENCED_PARAMETER(SmbLength);
|
|
UNREFERENCED_PARAMETER(Server);
|
|
|
|
ASSERT(Context->Header.Type == CONTEXT_TREECONNECT);
|
|
|
|
if (ErrorIndicator) {
|
|
Context->Header.ErrorType = NetError;
|
|
Context->Header.ErrorCode = RdrMapNetworkError(NetworkErrorCode);
|
|
goto ReturnError;
|
|
}
|
|
|
|
Context->SessionSetupError = Context->TreeConnectError = STATUS_SUCCESS;
|
|
Context->UserId = 0;
|
|
|
|
if (Smb->Command != SMB_COM_TREE_CONNECT) {
|
|
|
|
//
|
|
// I didn't like this tree connect - ignore it.
|
|
//
|
|
|
|
InternalError(("Incorrect tree connect response"));
|
|
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
goto ReturnError;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status = RdrMapSmbError(Smb, Server))) {
|
|
|
|
Context->Header.ErrorType = NoError;
|
|
Context->Header.ErrorCode = STATUS_SUCCESS;
|
|
|
|
Context->TreeConnectError = Status;
|
|
|
|
goto ReturnError;
|
|
|
|
}
|
|
|
|
//
|
|
// Everything is ok now, all we have to do is copy data from the SMB.
|
|
//
|
|
|
|
TreeConnect = (PRESP_TREE_CONNECT)(Smb+1);
|
|
|
|
Context->TreeId = SmbGetUshort(&TreeConnect->Tid);
|
|
|
|
Context->BufferSize = SmbGetUshort(&TreeConnect->MaxBufferSize);
|
|
|
|
Context->TreeConnectError = STATUS_SUCCESS;
|
|
|
|
ReturnError:
|
|
KeSetEvent(&Context->Header.KernelEvent, IO_NETWORK_INCREMENT, FALSE); // Wake up the caller
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
DBGSTATIC
|
|
STANDARD_CALLBACK_HEADER (
|
|
TreeConnectCallback
|
|
)
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called from the receive indication event handler to
|
|
handle the response to a treeconnect&x SMB.
|
|
|
|
|
|
Arguments:
|
|
|
|
IN PSMB_HEADER Smb - SMB response from server.
|
|
IN PMPX_ENTRY MpxEntry - MPX table entry for request.
|
|
IN PVOID Context - Context from caller.
|
|
IN BOOLEAN ErrorIndicator - TRUE if error indication
|
|
IN NTSTATUS NetworkErrorCode OPTIONAL - Network error if error indication.
|
|
IN OUT PIRP *Irp - IRP from TDI
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - STATUS_PENDING if we are to complete the request
|
|
|
|
--*/
|
|
{
|
|
PRESP_TREE_CONNECT_ANDX TreeConnect;
|
|
PRESP_21_TREE_CONNECT_ANDX LM21TreeConnect;
|
|
PTREECONNECTCONTEXT Context = Ctx;
|
|
PVOID p;
|
|
ULONG ByteCount;
|
|
NTSTATUS Status;
|
|
USHORT OptionalSupport;
|
|
|
|
DISCARDABLE_CODE(RdrVCDiscardableSection);
|
|
|
|
UNREFERENCED_PARAMETER(MpxEntry);
|
|
UNREFERENCED_PARAMETER(Irp);
|
|
UNREFERENCED_PARAMETER(Server);
|
|
|
|
ASSERT(Context->Header.Type == CONTEXT_TREECONNECT);
|
|
|
|
if (ErrorIndicator) {
|
|
Context->Header.ErrorType = NetError;
|
|
Context->Header.ErrorCode = RdrMapNetworkError(NetworkErrorCode);
|
|
goto ReturnError;
|
|
}
|
|
|
|
if ((Smb->Command != SMB_COM_TREE_CONNECT_ANDX) &&
|
|
(Smb->Command != SMB_COM_SESSION_SETUP_ANDX)) {
|
|
|
|
//
|
|
// I didn't like this tree connect - ignore it.
|
|
//
|
|
|
|
InternalError(("Incorrect Tree connect response"));
|
|
|
|
Context->Header.ErrorType = SMBError;
|
|
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
|
|
goto ReturnError;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status = RdrMapSmbError(Smb, Server))) {
|
|
|
|
Context->Header.ErrorType = NoError;
|
|
Context->Header.ErrorCode = STATUS_SUCCESS;
|
|
|
|
//
|
|
// One of the two requests in the SMB failed, we have to
|
|
// figure out which one failed.
|
|
//
|
|
|
|
if (Smb->Command==SMB_COM_SESSION_SETUP_ANDX) {
|
|
|
|
//
|
|
// This was a session setup&X command. This means that
|
|
// either the SessionSetup&X failed, or the TreeConnect&X failed.
|
|
//
|
|
// We determine which one failed by checking the SMB_COM2 field
|
|
// of the SMB. if it's TreeConnect&X, then the SessionSetup
|
|
// succeeded, and the TreeConnect failed, otherwise, it was
|
|
// the session setup that failed.
|
|
|
|
PGENERIC_ANDX AndX = (PGENERIC_ANDX )(Smb+1);
|
|
|
|
// DbgBreakPoint();
|
|
|
|
if (AndX->AndXCommand == SMB_COM_TREE_CONNECT_ANDX) {
|
|
|
|
//
|
|
// The session setup succeeded, and the Tree connect
|
|
// failed. Finish up the SessionSetup processing and
|
|
// return the error for the tree connect.
|
|
//
|
|
|
|
Context->UserId = SmbGetUshort(&Smb->Uid);
|
|
|
|
Context->SessionSetupError = STATUS_SUCCESS;
|
|
|
|
Context->TreeConnectError = Status;
|
|
|
|
} else {
|
|
|
|
Context->SessionSetupError = Status;
|
|
}
|
|
|
|
goto ReturnError;
|
|
|
|
} else {
|
|
|
|
|
|
//
|
|
// This was a TreeConnect&X command. There will only be an
|
|
// error from this operation.
|
|
//
|
|
|
|
Context->SessionSetupError = STATUS_SUCCESS;
|
|
|
|
Context->TreeConnectError = Status;
|
|
|
|
goto ReturnError;
|
|
}
|
|
}
|
|
|
|
Context->SessionSetupError = Context->TreeConnectError = STATUS_SUCCESS;
|
|
|
|
//
|
|
// Everything is ok now, all we have to do is copy the TID from the SMB.
|
|
//
|
|
|
|
Context->UserId = SmbGetUshort(&Smb->Uid);
|
|
Context->TreeId = SmbGetUshort(&Smb->Tid);
|
|
|
|
if (Smb->Command == SMB_COM_SESSION_SETUP_ANDX) {
|
|
|
|
PGENERIC_ANDX AndX = (PGENERIC_ANDX )(Smb+1);
|
|
PRESP_SESSION_SETUP_ANDX SessionSetup = (PRESP_SESSION_SETUP_ANDX )(Smb+1);
|
|
|
|
//
|
|
// If the server indicated that we should use the Lan Manager
|
|
// session key, then indicate this so we will copy the keys properly.
|
|
//
|
|
|
|
if (FlagOn(SmbGetUshort(&SessionSetup->Action), SMB_SETUP_USE_LANMAN_KEY)) {
|
|
Context->UseLmSessionSetupKey = TRUE;
|
|
}
|
|
|
|
//
|
|
// If there is no &X command associated with the request, then
|
|
// we don't have to do any more processing. We're done.
|
|
//
|
|
|
|
if (AndX->AndXCommand == SMB_COM_NO_ANDX_COMMAND) {
|
|
|
|
goto ReturnError;
|
|
}
|
|
|
|
TreeConnect = (PRESP_TREE_CONNECT_ANDX)((PCHAR )Smb+
|
|
SmbGetUshort(&AndX->AndXOffset));
|
|
|
|
LM21TreeConnect = (PRESP_21_TREE_CONNECT_ANDX) TreeConnect;
|
|
} else {
|
|
|
|
TreeConnect = (PRESP_TREE_CONNECT_ANDX )(Smb+1);
|
|
|
|
LM21TreeConnect = (PRESP_21_TREE_CONNECT_ANDX) TreeConnect;
|
|
|
|
}
|
|
|
|
//
|
|
// The tree connect needs to fit in the indicated data, as does the
|
|
// TreeConnect&X portion (including the ByteCount).
|
|
//
|
|
|
|
if (((ULONG)TreeConnect - (ULONG)Smb >= *SmbLength) ||
|
|
(((ULONG)TreeConnect+(TreeConnect->WordCount*sizeof(WORD))+sizeof(WORD)) - (ULONG)Smb >= *SmbLength)) {
|
|
|
|
//
|
|
// The TreeConnect&X itself doesn't fit into the users buffer.
|
|
//
|
|
// We need to post a receive for this buffer.
|
|
//
|
|
|
|
//
|
|
// We should only see an SMB that goes past the end of the
|
|
// indicated data on a Lanman 2.1 server or greater.
|
|
//
|
|
|
|
ASSERT (FlagOn(Server->Capabilities, DF_LANMAN21));
|
|
|
|
if (!FlagOn(Server->Capabilities, DF_LANMAN21)) {
|
|
Context->Header.ErrorType = SMBError;
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
goto ReturnError;
|
|
}
|
|
|
|
*SmbLength = 0;
|
|
|
|
//
|
|
// We are about to return this IRP, so activate the receive complete
|
|
// event in the context header so that RdrNetTranceive will wait
|
|
// until this receive completes (in the case that we might time out
|
|
// the VC after this receive completes, we don't want to free the IRP
|
|
// to early).
|
|
//
|
|
|
|
KeClearEvent(&Context->ReceiveCompleteEvent);
|
|
|
|
RdrStartReceiveForMpxEntry(MpxEntry, Context->ReceiveIrp);
|
|
|
|
*Irp = Context->ReceiveIrp;
|
|
|
|
ASSERT (*Irp != NULL);
|
|
|
|
Context->BufferTooShort = TRUE;
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
if (TreeConnect->WordCount == 2) {
|
|
p = (PVOID)TreeConnect->Buffer;
|
|
ByteCount = SmbGetUshort(&TreeConnect->ByteCount);
|
|
} else if (TreeConnect->WordCount == 3) {
|
|
|
|
ByteCount = SmbGetUshort(&LM21TreeConnect->ByteCount);
|
|
|
|
OptionalSupport = SmbGetUshort(&LM21TreeConnect->OptionalSupport);
|
|
Context->ShareIsInDfs = OptionalSupport & SMB_SHARE_IS_IN_DFS;
|
|
|
|
//
|
|
// Only LM 2.1 servers should return a word count of 3.
|
|
//
|
|
|
|
if (!FlagOn(Server->Capabilities, DF_LANMAN21)) {
|
|
Context->Header.ErrorType = SMBError;
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
goto ReturnError;
|
|
}
|
|
|
|
p = (PVOID)LM21TreeConnect->Buffer;
|
|
|
|
} else {
|
|
Context->Header.ErrorType = SMBError;
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
goto ReturnError;
|
|
}
|
|
|
|
//
|
|
// If the tree connect portion is beyond the indicated data,
|
|
// we need to process the request at task time in the buffer we already
|
|
// have available.
|
|
//
|
|
|
|
if ( ( ((ULONG)p - (ULONG)Smb) + ByteCount ) > *SmbLength
|
|
#ifndef _M_IX86
|
|
//
|
|
// If this is a non Intel platform, some of the
|
|
// transports will not be able to copy data at indication time (depending
|
|
// on the type of card in the machine). On these transports,
|
|
// always post a receive for the data and process it at task time.
|
|
//
|
|
|
|
|| (!FlagOn(ReceiveFlags, TDI_RECEIVE_COPY_LOOKAHEAD) &&
|
|
FlagOn(Server->Capabilities, DF_LANMAN21))
|
|
#endif
|
|
) {
|
|
|
|
#ifdef _M_IX86
|
|
ASSERT (FlagOn(Server->Capabilities, DF_LANMAN21));
|
|
|
|
if (!FlagOn(ReceiveFlags, TDI_RECEIVE_COPY_LOOKAHEAD) &&
|
|
!FlagOn(Server->Capabilities, DF_LANMAN21)) {
|
|
Context->Header.ErrorType = SMBError;
|
|
Context->Header.ErrorCode = STATUS_INVALID_NETWORK_RESPONSE;
|
|
goto ReturnError;
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// In this case, we take no data out of the SMB.
|
|
//
|
|
|
|
*SmbLength = 0;
|
|
|
|
//
|
|
// We are about to return this IRP, so activate the receive complete
|
|
// event in the context header so that RdrNetTranceive will wait
|
|
// until this receive completes (in the case that we might time out
|
|
// the VC after this receive completes, we don't want to free the IRP
|
|
// to early).
|
|
//
|
|
|
|
KeClearEvent(&Context->ReceiveCompleteEvent);
|
|
|
|
RdrStartReceiveForMpxEntry(MpxEntry, Context->ReceiveIrp);
|
|
|
|
*Irp = Context->ReceiveIrp;
|
|
|
|
ASSERT (*Irp != NULL);
|
|
|
|
Context->BufferTooShort = TRUE;
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
Status = ProcessTreeConnectAndXBuffer(Smb, p, Context, Server, *SmbLength - ((PCHAR)p-(PCHAR)Smb), ReceiveFlags);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
Context->Header.ErrorType = SMBError;
|
|
Context->Header.ErrorCode = Status;
|
|
}
|
|
|
|
ReturnError:
|
|
KeSetEvent(&Context->Header.KernelEvent, IO_NETWORK_INCREMENT, FALSE); // Wake up the caller
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
#ifdef _M_IX86
|
|
_inline
|
|
#endif
|
|
ULONG
|
|
Safestrlen(
|
|
CHAR *p,
|
|
LONG MaxLength
|
|
)
|
|
/*++
|
|
|
|
Safestrlen - Strlen that won't exceed MaxLength
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to determine the length of an OEM string.
|
|
|
|
Arguments:
|
|
CHAR *p - The string to count.
|
|
ULONG MaxLength - The maximum length to look at.
|
|
|
|
|
|
Return Value:
|
|
|
|
Number of bytes in the string (or MaxLength)
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG Count = 0;
|
|
|
|
while (MaxLength >= 0 && (*p++ != 0)) {
|
|
MaxLength -= sizeof(CHAR);
|
|
Count += 1;
|
|
}
|
|
|
|
return Count;
|
|
}
|
|
|
|
#ifdef _M_IX86
|
|
_inline
|
|
#endif
|
|
ULONG
|
|
Safewcslen(
|
|
UNALIGNED WCHAR *p,
|
|
LONG MaxLength
|
|
)
|
|
|
|
/*++
|
|
|
|
Safestrlen - Strlen that won't exceed MaxLength
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to determine the length of an OEM string.
|
|
|
|
Arguments:
|
|
CHAR *p - The string to count.
|
|
LONG MaxLength - The maximum length to look at.
|
|
|
|
|
|
Return Value:
|
|
|
|
Number of bytes in the string (or MaxLength)
|
|
|
|
--*/
|
|
{
|
|
ULONG Count = 0;
|
|
while (MaxLength >= 0 && *p++ != UNICODE_NULL) {
|
|
MaxLength -= sizeof(WCHAR);
|
|
Count += 1;
|
|
}
|
|
|
|
return Count;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
ProcessTreeConnectAndXBuffer(
|
|
IN PSMB_HEADER SmbStart,
|
|
IN PVOID p,
|
|
IN PTREECONNECTCONTEXT Context,
|
|
IN PSERVERLISTENTRY Server,
|
|
IN ULONG SmbLength,
|
|
IN ULONG ReceiveFlags
|
|
)
|
|
/*++
|
|
|
|
ProcessTreeConnectAndXBuffer - Process the buffer portion of a tree
|
|
connect&x SMB.
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to process the buffer portion of a TreeConnect&X
|
|
SMB.
|
|
|
|
Arguments:
|
|
|
|
|
|
Return Value:
|
|
|
|
Status of unicode conversion of device type if appropriate.
|
|
|
|
--*/
|
|
{
|
|
ULONG i;
|
|
PCHAR Originalp = p;
|
|
|
|
DISCARDABLE_CODE(RdrVCDiscardableSection);
|
|
|
|
//
|
|
// Scan through the connection types to find out which one this is.
|
|
//
|
|
|
|
for (i=0 ; i < RdrNumConnectTypes - 1 ; i ++) {
|
|
if (strncmp(RdrConnectTypes[i], p, strlen(RdrConnectTypes[i]))==0) {
|
|
Context->Type = i;
|
|
(PCHAR )p += strlen(RdrConnectTypes[i])+1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we didn't find a connection type, fail the connection request.
|
|
//
|
|
|
|
if (p == Originalp) {
|
|
return STATUS_UNEXPECTED_NETWORK_ERROR;
|
|
}
|
|
|
|
//
|
|
// If we've swallowed all the input, return now.
|
|
//
|
|
|
|
if ((ULONG)((PCHAR)p - Originalp) == SmbLength) {
|
|
|
|
//
|
|
// Initialize the file system type of the connection to null.
|
|
//
|
|
|
|
RtlZeroMemory(Context->FileSystemType, sizeof(Context->FileSystemType));
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// If this is a Lanman 2.1 server, get the file system type
|
|
// from the SMB.
|
|
//
|
|
|
|
if (FlagOn(Server->Capabilities, DF_LANMAN21)) {
|
|
|
|
if (FlagOn(Server->Capabilities, DF_UNICODE)) {
|
|
ULONG plen;
|
|
|
|
//
|
|
// If the pointer is unaligned W.R.T. the buffer, align it right
|
|
// now. Please note that this does NOT make the pointer aligned
|
|
// in memory - Savewcslen has to be able to deal with unaligned
|
|
// pointers.
|
|
//
|
|
|
|
if (((ULONG)p - (ULONG)SmbStart) & 1) {
|
|
(PCHAR)p += 1;
|
|
}
|
|
|
|
//
|
|
// Calculate the length of the pointer.
|
|
//
|
|
|
|
plen = Safewcslen(p, SmbLength - ((PCHAR)p - Originalp))+1;
|
|
|
|
TdiCopyLookaheadData(Context->FileSystemType, p, (MIN(plen, LM20_DEVLEN) * sizeof(WCHAR)), ReceiveFlags);
|
|
|
|
} else {
|
|
ULONG plen;
|
|
|
|
//
|
|
// Calculate the length of the pointer.
|
|
//
|
|
|
|
plen = Safestrlen(p, SmbLength - ((PCHAR)p - Originalp))+1;
|
|
|
|
TdiCopyLookaheadData(Context->FileSystemType, p, MIN(plen, LM20_DEVLEN), ReceiveFlags);
|
|
|
|
}
|
|
|
|
} else {
|
|
RdrWriteErrorLogEntry(
|
|
Server,
|
|
IO_ERR_LAYERED_FAILURE,
|
|
EVENT_RDR_INVALID_SMB,
|
|
STATUS_SUCCESS,
|
|
Originalp,
|
|
(USHORT)SmbLength
|
|
);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
TreeConnectComplete (
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PVOID Ctx
|
|
)
|
|
/*++
|
|
|
|
TreeConnectComplete - Final completion for user request.
|
|
|
|
Routine Description:
|
|
|
|
This routine is called on final completion of the TDI_Receive
|
|
request from the transport. If the request completed successfully,
|
|
this routine will complete the request with no error, if the receive
|
|
completed with an error, it will flag the error and complete the
|
|
request.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - Device structure that the request completed on.
|
|
Irp - The Irp that completed.
|
|
Context - Context information for completion.
|
|
|
|
Return Value:
|
|
|
|
Return value to be returned from receive indication routine.
|
|
--*/
|
|
|
|
|
|
{
|
|
PTREECONNECTCONTEXT Context = Ctx;
|
|
|
|
DISCARDABLE_CODE(RdrVCDiscardableSection);
|
|
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
|
|
dprintf(DPRT_CONNECT, ("TreeConnectComplete. Irp: %lx, Context: %lx\n", Irp, Context));
|
|
|
|
ASSERT(Context->Header.Type == CONTEXT_TREECONNECT);
|
|
|
|
RdrCompleteReceiveForMpxEntry(Context->Header.MpxTableEntry, Irp);
|
|
|
|
if (NT_SUCCESS(Irp->IoStatus.Status)) {
|
|
|
|
//
|
|
// Setting ReceiveIrpProcessing will cause the checks in
|
|
// RdrNetTranceive to check the incoming SMB for errors.
|
|
//
|
|
|
|
Context->ReceiveLength = Irp->IoStatus.Information;
|
|
|
|
} else {
|
|
|
|
RdrStatistics.FailedCompletionOperations += 1;
|
|
Context->Header.ErrorType = NetError;
|
|
Context->Header.ErrorCode = RdrMapNetworkError(Irp->IoStatus.Status);
|
|
|
|
}
|
|
|
|
//
|
|
// Mark that the kernel event indicating that this I/O operation has
|
|
// completed is done.
|
|
//
|
|
// Please note that we need TWO events here. The first event is
|
|
// set to the signalled state when the multiplexed exchange is
|
|
// completed, while the second is set to the signalled status when
|
|
// this receive request has completed,
|
|
//
|
|
// The KernelEvent MUST BE SET FIRST, THEN the ReceiveCompleteEvent.
|
|
// This is because the KernelEvent may already be set, in which case
|
|
// setting the ReceiveCompleteEvent first would let the thread that's
|
|
// waiting on the events run, and delete the KernelEvent before we
|
|
// set it.
|
|
//
|
|
|
|
KeSetEvent(&Context->Header.KernelEvent, IO_NETWORK_INCREMENT, FALSE);
|
|
KeSetEvent(&Context->ReceiveCompleteEvent, IO_NETWORK_INCREMENT, FALSE);
|
|
|
|
//
|
|
// Short circuit I/O completion on this request now.
|
|
//
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildSessionSetupAndX(
|
|
IN PSMB_BUFFER SmbBuffer,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds a session setup&x SMB in the specified SMB buffer
|
|
.
|
|
Arguments:
|
|
|
|
IN PSMB_HEADER Smb, - Supplies a pointer to the SMB buffer to fill in
|
|
IN PSECURITY_ENTRY Se - Supplies security information about the user
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
Notes:
|
|
When this routine returns, the AndXOffset field of the SessionSetup
|
|
is set to the appropriate offset in the SMB for use in BuildTreeConnect.
|
|
|
|
If the caller is only going to send a SessionSetup&X SMB, then
|
|
it is the callers responsibility to zero out the AndXOffset.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PSERVERLISTENTRY Server = Connection->Server;
|
|
PSMB_HEADER Smb = (PSMB_HEADER )SmbBuffer->Buffer;
|
|
PREQ_SESSION_SETUP_ANDX SessionSetup;
|
|
PREQ_NT_SESSION_SETUP_ANDX NtSessionSetup;
|
|
PVOID p;
|
|
CHAR UNALIGNED *BufferStart;
|
|
UNICODE_STRING UserName;
|
|
UNICODE_STRING LogonDomain;
|
|
// PTRANSPORT_CONNECTION Connection;
|
|
|
|
PAGED_CODE();
|
|
|
|
LogonDomain.Buffer = NULL;
|
|
UserName.Buffer = NULL;
|
|
|
|
//
|
|
// Set up the Session Setup&X SMB header.
|
|
//
|
|
|
|
NtSessionSetup = (PREQ_NT_SESSION_SETUP_ANDX )(Smb+1);
|
|
|
|
SessionSetup = (PREQ_SESSION_SETUP_ANDX )(Smb+1);
|
|
|
|
Smb->Command = SMB_COM_SESSION_SETUP_ANDX;
|
|
|
|
if (Server->Capabilities & DF_NTNEGOTIATE) {
|
|
|
|
SessionSetup->WordCount = 13; // Set word count in SMB.
|
|
|
|
} else {
|
|
SessionSetup->WordCount = 10; // Set word count in SMB.
|
|
}
|
|
|
|
SessionSetup->AndXCommand = 0xff; // No ANDX
|
|
SessionSetup->AndXReserved = 0x00; // Reserved (MBZ)
|
|
|
|
ASSERT (FIELD_OFFSET(REQ_SESSION_SETUP_ANDX, AndXOffset) ==
|
|
FIELD_OFFSET(REQ_NT_SESSION_SETUP_ANDX, AndXOffset));
|
|
|
|
ASSERT (FIELD_OFFSET(REQ_SESSION_SETUP_ANDX, WordCount) ==
|
|
FIELD_OFFSET(REQ_NT_SESSION_SETUP_ANDX, WordCount));
|
|
|
|
ASSERT (FIELD_OFFSET(REQ_SESSION_SETUP_ANDX, AndXReserved) ==
|
|
FIELD_OFFSET(REQ_NT_SESSION_SETUP_ANDX, AndXReserved));
|
|
|
|
ASSERT (FIELD_OFFSET(REQ_SESSION_SETUP_ANDX, MaxBufferSize) ==
|
|
FIELD_OFFSET(REQ_NT_SESSION_SETUP_ANDX, MaxBufferSize));
|
|
|
|
ASSERT (FIELD_OFFSET(REQ_SESSION_SETUP_ANDX, MaxMpxCount) ==
|
|
FIELD_OFFSET(REQ_NT_SESSION_SETUP_ANDX, MaxMpxCount));
|
|
|
|
ASSERT (FIELD_OFFSET(REQ_SESSION_SETUP_ANDX, VcNumber) ==
|
|
FIELD_OFFSET(REQ_NT_SESSION_SETUP_ANDX, VcNumber));
|
|
|
|
ASSERT (FIELD_OFFSET(REQ_SESSION_SETUP_ANDX, SessionKey) ==
|
|
FIELD_OFFSET(REQ_NT_SESSION_SETUP_ANDX, SessionKey));
|
|
|
|
|
|
SmbPutUshort(&SessionSetup->AndXOffset, 0x0000); // No AndX as of yet.
|
|
|
|
//
|
|
// Since we can allocate pool dynamically, we set our buffer size
|
|
// to match that of the server.
|
|
//
|
|
|
|
SmbPutUshort(&SessionSetup->MaxBufferSize, (USHORT)Server->BufferSize);
|
|
SmbPutUshort(&SessionSetup->MaxMpxCount, Server->MaximumRequests);
|
|
|
|
//
|
|
// The number of VC number field is set to the number of sessions
|
|
// outstanding on a VC.
|
|
//
|
|
// Milans : WHY? Why should the *VC* number be set to the number of
|
|
// *SESSIONS*?
|
|
//
|
|
// This breaks connection to HP-UX if there is a null session to
|
|
// the server already. So, we only do this for servers that
|
|
// negotiate >= DF_LANMAN20
|
|
//
|
|
|
|
if (Server->Capabilities & DF_LANMAN20) {
|
|
SmbPutUshort(&SessionSetup->VcNumber, RdrGetNumberSessions(Server));
|
|
} else {
|
|
SmbPutUshort(&SessionSetup->VcNumber, 0);
|
|
}
|
|
|
|
SmbPutUlong(&SessionSetup->SessionKey, Server->SessionKey);
|
|
|
|
if (Server->Capabilities & DF_NTNEGOTIATE) {
|
|
SmbPutUlong(&NtSessionSetup->Reserved, 0);
|
|
SmbPutUlong(&NtSessionSetup->Capabilities, CAP_NT_STATUS | CAP_UNICODE | CAP_LEVEL_II_OPLOCKS | CAP_NT_SMBS);
|
|
|
|
BufferStart = NtSessionSetup->Buffer;
|
|
p = (PVOID)BufferStart;
|
|
|
|
} else {
|
|
SmbPutUlong(&SessionSetup->Reserved, 0);
|
|
BufferStart = SessionSetup->Buffer;
|
|
p = (PVOID)BufferStart;
|
|
}
|
|
|
|
try {
|
|
|
|
//
|
|
// Share level security servers ignore the password in the SessionSetupX
|
|
// SMB, so don't set the password in the SMB.
|
|
//
|
|
// User level security servers accept the password in the SessionSetup,
|
|
// and ignore the password in the Treeconnect&X SMB.
|
|
//
|
|
|
|
if (Server->UserSecurity) {
|
|
|
|
//
|
|
// User level security server.
|
|
//
|
|
|
|
if (Server->EncryptPasswords) {
|
|
|
|
if (Server->Capabilities & DF_NTNEGOTIATE) {
|
|
STRING CaseSensitivePassword;
|
|
STRING CaseInsensitivePassword;
|
|
|
|
Status = RdrGetChallengeResponse(Server->CryptKey, Se,
|
|
&CaseSensitivePassword,
|
|
&CaseInsensitivePassword,
|
|
&UserName, // UserName
|
|
&LogonDomain, // Domain
|
|
FALSE); // allow default password
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
SmbPutUshort(&NtSessionSetup->CaseInsensitivePasswordLength, CaseInsensitivePassword.Length);
|
|
|
|
RtlCopyMemory(p, CaseInsensitivePassword.Buffer, CaseInsensitivePassword.Length);
|
|
|
|
(PCHAR)p += CaseInsensitivePassword.Length;
|
|
|
|
if (CaseInsensitivePassword.Length != 0) {
|
|
FREE_POOL(CaseInsensitivePassword.Buffer);
|
|
}
|
|
|
|
SmbPutUshort(&NtSessionSetup->CaseSensitivePasswordLength, CaseSensitivePassword.Length);
|
|
|
|
RtlCopyMemory(p, CaseSensitivePassword.Buffer, CaseSensitivePassword.Length);
|
|
|
|
(PCHAR)p += CaseSensitivePassword.Length;
|
|
|
|
if (CaseSensitivePassword.Length != 0) {
|
|
FREE_POOL(CaseSensitivePassword.Buffer);
|
|
}
|
|
|
|
} else {
|
|
STRING EncryptedPassword;
|
|
|
|
if (Server->Capabilities & (DF_UNICODE | DF_MIXEDCASEPW)) {
|
|
Status = RdrGetChallengeResponse(Server->CryptKey, Se,
|
|
&EncryptedPassword, NULL, &UserName, &LogonDomain,
|
|
FALSE); // allow default password
|
|
} else {
|
|
Status = RdrGetChallengeResponse(Server->CryptKey, Se,
|
|
NULL, &EncryptedPassword, &UserName, &LogonDomain,
|
|
FALSE); // allow default password
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
SmbPutUshort(&SessionSetup->PasswordLength, EncryptedPassword.Length);
|
|
|
|
RtlCopyMemory(p, EncryptedPassword.Buffer, EncryptedPassword.Length);
|
|
|
|
(PCHAR)p += EncryptedPassword.Length;
|
|
|
|
if (EncryptedPassword.Length != 0) {
|
|
FREE_POOL(EncryptedPassword.Buffer);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
if (Se->Flags & SE_USE_DEFAULT_PASS) {
|
|
|
|
//
|
|
// The user wants us to send the default logon password
|
|
// to a non encrypting server. We cannot support this,
|
|
// so return an error.
|
|
//
|
|
|
|
try_return(Status = STATUS_ACCESS_DENIED);
|
|
|
|
} else {
|
|
|
|
//
|
|
// This is a non-encrypting user level security server,
|
|
// and the user has specified a password (so we can send
|
|
// it clear text, since they indicated they wanted to
|
|
// bypass security).
|
|
//
|
|
|
|
if (Se->Flags & SE_USE_DEFAULT_USER) {
|
|
|
|
Status = RdrGetUserName(&Se->LogonId, &UserName);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Duplicate the user's supplied user name to put
|
|
// into the SMB.
|
|
//
|
|
|
|
Status = RdrpDuplicateUnicodeStringWithString(&UserName, &Se->UserName, NonPagedPool, FALSE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// Initialize the logon domain with our primary domain
|
|
// in case this is a LM 2.1+ server.
|
|
//
|
|
|
|
Status = RdrpDuplicateUnicodeStringWithString(&LogonDomain, &RdrPrimaryDomain, NonPagedPool, FALSE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
if (Server->Capabilities & DF_UNICODE) {
|
|
|
|
UNICODE_STRING DestinationString;
|
|
|
|
p = ALIGN_SMB_WSTR(p);
|
|
DestinationString.Buffer = p;
|
|
|
|
DestinationString.MaximumLength = (LM20_PWLEN+1)*sizeof(WCHAR);
|
|
|
|
DestinationString.Length = 0;
|
|
|
|
RdrCopyUnicodeStringToUnicode(&p, &Se->Password, TRUE);
|
|
|
|
if (!(Server->Capabilities & DF_MIXEDCASEPW)) {
|
|
|
|
Status = RtlUpcaseUnicodeString(&DestinationString, &DestinationString, FALSE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// This server doesn't encrypt, and we have a clear text
|
|
// password for us to send, so send it.
|
|
//
|
|
|
|
SmbPutUshort(&SessionSetup->PasswordLength,
|
|
(USHORT )(Se->Password.Length+sizeof(WCHAR)));
|
|
*((PWCH)p)++ = UNICODE_NULL;
|
|
|
|
} else {
|
|
OEM_STRING DestinationString;
|
|
UNICODE_STRING Password;
|
|
|
|
DestinationString.Buffer = p;
|
|
|
|
DestinationString.MaximumLength = LM20_PWLEN+1;
|
|
|
|
DestinationString.Length = 0;
|
|
|
|
if (Se->Password.MaximumLength != 0) {
|
|
Password.Buffer = ALLOCATE_POOL(PagedPool, Se->Password.MaximumLength, POOL_PASSWORD);
|
|
} else {
|
|
Password.Buffer = NULL;
|
|
}
|
|
|
|
Password.MaximumLength = Se->Password.MaximumLength;
|
|
|
|
//
|
|
// If the server doesn't support case sensitive
|
|
// passwords, uppercase the password
|
|
//
|
|
|
|
if (!(Server->Capabilities & DF_MIXEDCASEPW)) {
|
|
RtlUpcaseUnicodeString(&Password, &Se->Password, FALSE);
|
|
} else {
|
|
RtlCopyUnicodeString(&Password, &Se->Password);
|
|
}
|
|
|
|
Status = RtlUnicodeStringToOemString(&DestinationString, &Password, FALSE);
|
|
|
|
if (Password.Buffer != NULL) {
|
|
FREE_POOL(Password.Buffer);
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
//
|
|
// This server doesn't encrypt, and we have a clear text
|
|
// password for us to send, so send it.
|
|
//
|
|
|
|
SmbPutUshort(&SessionSetup->PasswordLength,
|
|
(USHORT )(DestinationString.Length+1));
|
|
|
|
(PCHAR)p+= DestinationString.Length;
|
|
|
|
*((PCHAR)p)++ = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy over the user name into the SMB.
|
|
//
|
|
|
|
if (Server->Capabilities & DF_UNICODE) {
|
|
if (!(Server->Capabilities & DF_NTNEGOTIATE )) {
|
|
UNICODE_STRING UcaseString;
|
|
|
|
if (UserName.Length != 0) {
|
|
Status = RtlUpcaseUnicodeString(&UcaseString, &UserName, TRUE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
} else {
|
|
UcaseString = UserName;
|
|
}
|
|
|
|
p = ALIGN_SMB_WSTR(p);
|
|
RdrCopyUnicodeStringToUnicode(&p, &UcaseString, TRUE);
|
|
*((PWCH)p)++ = '\0';
|
|
|
|
if (UcaseString.Length != 0) {
|
|
RtlFreeUnicodeString(&UcaseString);
|
|
}
|
|
|
|
} else {
|
|
p = ALIGN_SMB_WSTR(p);
|
|
RdrCopyUnicodeStringToUnicode(&p, &UserName, TRUE);
|
|
*((PWCH)p)++ = '\0';
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!(Server->Capabilities & DF_NTNEGOTIATE )) {
|
|
UNICODE_STRING UcaseString;
|
|
|
|
if (UserName.Length != 0) {
|
|
Status = RtlUpcaseUnicodeString(&UcaseString, &UserName, TRUE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
} else {
|
|
UcaseString = UserName;
|
|
}
|
|
|
|
Status = RdrCopyUnicodeStringToAscii((PUCHAR *)&p, &UcaseString, TRUE, (USHORT)(SMB_BUFFER_SIZE - ((PUCHAR)p-(PUCHAR)Smb)));
|
|
|
|
*((PCHAR)p)++ = '\0';
|
|
|
|
if (UcaseString.Length) {
|
|
RtlFreeUnicodeString(&UcaseString);
|
|
}
|
|
|
|
} else {
|
|
|
|
Status = RdrCopyUnicodeStringToAscii((PUCHAR *)&p, &UserName, TRUE, (USHORT)(SMB_BUFFER_SIZE - ((PUCHAR)p-(PUCHAR)Smb)));
|
|
|
|
*((PCHAR)p)++ = '\0';
|
|
}
|
|
}
|
|
|
|
|
|
} else {
|
|
//
|
|
// This is a share level security server.
|
|
//
|
|
|
|
Status = RdrpDuplicateUnicodeStringWithString(&LogonDomain, &RdrPrimaryDomain, PagedPool, FALSE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
if (Server->Capabilities & DF_NTNEGOTIATE) {
|
|
SmbPutUshort(&NtSessionSetup->CaseSensitivePasswordLength, 1);
|
|
*((PCHAR)p)++ = '\0';
|
|
|
|
SmbPutUshort(&NtSessionSetup->CaseInsensitivePasswordLength, 1);
|
|
*((PCHAR)p)++ = '\0';
|
|
} else {
|
|
|
|
SmbPutUshort(&SessionSetup->PasswordLength, 1);
|
|
*((PCHAR)p)++ = '\0';
|
|
}
|
|
|
|
//
|
|
// Now copy in the user name - it's ignored, but....
|
|
//
|
|
|
|
if (Server->Capabilities & DF_UNICODE) {
|
|
p = ALIGN_SMB_WSTR(p);
|
|
Status = RdrCopyUnicodeUserName((PWSTR *)&p, Se);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
ULONG i;
|
|
PTDI_ADDRESS_NETBIOS Address;
|
|
OEM_STRING AString;
|
|
UNICODE_STRING UString;
|
|
UCHAR Computername[ sizeof( Address->NetbiosName ) ];
|
|
|
|
ExAcquireResourceShared(&RdrDataResource, TRUE);
|
|
|
|
Address = &RdrData.ComputerName->Address[0].Address[0];
|
|
|
|
for (i = 0;i < sizeof(Address->NetbiosName) ; i ++) {
|
|
if (Address->NetbiosName[i] == ' ' || Address->NetbiosName[i] == '\0') {
|
|
break;
|
|
}
|
|
Computername[i] = Address->NetbiosName[i];
|
|
}
|
|
|
|
ExReleaseResource(&RdrDataResource);
|
|
|
|
AString.Buffer = Computername;
|
|
AString.Length = (USHORT)i;
|
|
AString.MaximumLength = sizeof( Computername );
|
|
|
|
UString.Buffer = p;
|
|
UString.MaximumLength = (USHORT)(sizeof(Computername)*sizeof(WCHAR));
|
|
|
|
Status = RtlOemStringToUnicodeString(&UString, &AString, FALSE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return(Status);
|
|
}
|
|
|
|
*((PWCH)p) += (UString.Length/sizeof(WCHAR));
|
|
|
|
}
|
|
|
|
*((PWCH)p)++ = UNICODE_NULL;
|
|
} else {
|
|
Status = RdrCopyUserName((PSZ *)&p, Se);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
//
|
|
// It is possible that we are logged on as system, and
|
|
// thus we want to use the computer name as the user
|
|
// name in the session setup SMB.
|
|
//
|
|
|
|
ULONG i;
|
|
PTDI_ADDRESS_NETBIOS Address;
|
|
|
|
ExAcquireResourceShared(&RdrDataResource, TRUE);
|
|
|
|
Address = &RdrData.ComputerName->Address[0].Address[0];
|
|
|
|
for (i = 0;i < sizeof(Address->NetbiosName) ; i ++) {
|
|
if (Address->NetbiosName[i] == ' ' || Address->NetbiosName[i] == '\0') {
|
|
break;
|
|
}
|
|
|
|
*((PCHAR)p)++ = Address->NetbiosName[i];
|
|
}
|
|
|
|
ExReleaseResource(&RdrDataResource);
|
|
}
|
|
|
|
*((PCHAR)p)++ = '\0';
|
|
}
|
|
}
|
|
|
|
if (Server->Capabilities & DF_LANMAN21) {
|
|
|
|
if (Server->Capabilities & DF_UNICODE) {
|
|
p = ALIGN_SMB_WSTR(p);
|
|
RdrCopyUnicodeStringToUnicode(&p, &LogonDomain, TRUE);
|
|
|
|
*((PWCH)p)++ = UNICODE_NULL;
|
|
|
|
} else {
|
|
Status = RdrCopyUnicodeStringToAscii((PUCHAR *)&p, &LogonDomain, TRUE, (USHORT)(SMB_BUFFER_SIZE - ((PUCHAR)p-(PUCHAR)Smb)));
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
|
|
*((PCHAR)p)++ = '\0';
|
|
}
|
|
|
|
if (Server->Capabilities & DF_UNICODE) {
|
|
//p = ALIGN_SMB_WSTR(p);
|
|
RdrCopyUnicodeStringToUnicode(&p, &RdrOperatingSystem, TRUE);
|
|
*((PWCH)p)++ = UNICODE_NULL;
|
|
} else {
|
|
Status = RdrCopyUnicodeStringToAscii((PUCHAR *)&p, &RdrOperatingSystem, TRUE, (USHORT)(SMB_BUFFER_SIZE - ((PUCHAR)p-(PUCHAR)Smb)));
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
*((PCHAR)p)++ = '\0';
|
|
}
|
|
|
|
if (Server->Capabilities & DF_UNICODE) {
|
|
//p = ALIGN_SMB_WSTR(p);
|
|
RdrCopyUnicodeStringToUnicode(&p, &RdrLanmanType, TRUE);
|
|
*((PWCH)p)++ = UNICODE_NULL;
|
|
} else {
|
|
Status = RdrCopyUnicodeStringToAscii((PUCHAR *)&p, &RdrLanmanType, TRUE, (USHORT)(SMB_BUFFER_SIZE - ((PUCHAR)p-(PUCHAR)Smb)));
|
|
if (!NT_SUCCESS(Status)) {
|
|
try_return(Status);
|
|
}
|
|
*((PCHAR)p)++ = '\0';
|
|
}
|
|
|
|
}
|
|
|
|
if (Server->Capabilities & DF_NTNEGOTIATE) {
|
|
SmbPutUshort(&NtSessionSetup->ByteCount, (USHORT)(((PCHAR)p) - BufferStart));
|
|
} else {
|
|
SmbPutUshort(&SessionSetup->ByteCount, (USHORT)(((PCHAR)p) - BufferStart));
|
|
}
|
|
|
|
SmbPutUshort(&SessionSetup->AndXOffset, (USHORT )(((PUCHAR)p) - (PUCHAR )Smb));
|
|
|
|
SmbBuffer->Mdl->ByteCount = (USHORT )(((PUCHAR)p)-(PUCHAR)Smb);
|
|
|
|
try_return(Status = STATUS_SUCCESS);
|
|
|
|
try_exit:NOTHING;
|
|
} finally {
|
|
|
|
//
|
|
// Free the pool used for the user name if pool was allocated.
|
|
//
|
|
|
|
if (UserName.Buffer != NULL) {
|
|
FREE_POOL(UserName.Buffer);
|
|
}
|
|
|
|
|
|
if (LogonDomain.Buffer != NULL) {
|
|
FREE_POOL(LogonDomain.Buffer);
|
|
}
|
|
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
#ifdef _CAIRO_
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildCairoSessionSetup(IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se,
|
|
IN PSERVERLISTENTRY Server,
|
|
PUCHAR pucIn,
|
|
ULONG ulIn,
|
|
OUT PVOID *SendData,
|
|
OUT PCLONG SendDataCount,
|
|
OUT PVOID *ReceiveData,
|
|
OUT PCLONG ReceiveDataCount)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds a the stuff for a session setup&x SMB in the
|
|
send data buffer, which will be sent as a trans2 SMB.
|
|
.
|
|
Arguments:
|
|
|
|
IN PSECURITY_ENTRY Se - Supplies security information about the user
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
PCHAR Blob = NULL;
|
|
ULONG BlobLength = 0;
|
|
PREQ_CAIRO_TRANS2_SESSION_SETUP BlobPointer;
|
|
PVOID BufPtr;
|
|
UNICODE_STRING KerberosText, TempPrincipal;
|
|
|
|
dprintf(DPRT_CAIRO, (" -- BuildCairoConnect\n"));
|
|
|
|
KerberosText.Length = KerberosText.MaximumLength = 0;
|
|
KerberosText.Buffer = NULL;
|
|
|
|
if(!Server->Principal.Length)
|
|
{
|
|
ULONG ulLen;
|
|
PWCHAR pPtr;
|
|
|
|
//
|
|
// We've no principal. Need to construct one
|
|
//
|
|
|
|
if((Server->DomainName.Length == 0)
|
|
||
|
|
(Server->DomainName.Buffer[0] != L'\\'))
|
|
{
|
|
//
|
|
// Invalid domain name. Bail out now
|
|
//
|
|
|
|
return(STATUS_NO_LOGON_SERVERS);
|
|
}
|
|
|
|
//
|
|
// we've a valid Kerberos domain. Build a principal name
|
|
// The name will be domainname:servname
|
|
//
|
|
|
|
ulLen = Server->DomainName.Length +
|
|
Server->Text.Length +
|
|
(2 * sizeof(WCHAR));
|
|
|
|
pPtr = ExAllocatePool(PagedPool,
|
|
ulLen);
|
|
if(!pPtr)
|
|
{
|
|
return(STATUS_NO_MEMORY);
|
|
}
|
|
|
|
TempPrincipal.Buffer = pPtr;
|
|
|
|
//
|
|
// construct the name
|
|
//
|
|
|
|
RtlMoveMemory(pPtr,
|
|
Server->DomainName.Buffer,
|
|
Server->DomainName.Length);
|
|
pPtr += Server->DomainName.Length / 2;
|
|
*pPtr++ = L':';
|
|
RtlMoveMemory(pPtr,
|
|
Server->Text.Buffer,
|
|
Server->Text.Length);
|
|
|
|
pPtr += Server->Text.Length / 2;
|
|
|
|
*pPtr = 0; // It's nice to be NULL terminated
|
|
|
|
//
|
|
// Fix up the counts
|
|
//
|
|
|
|
TempPrincipal.Length = Server->DomainName.Length +
|
|
Server->Text.Length +
|
|
sizeof(WCHAR);
|
|
TempPrincipal.MaximumLength = Server->Principal.Length +
|
|
sizeof(WCHAR);
|
|
}
|
|
else
|
|
{
|
|
TempPrincipal.Buffer = 0;
|
|
}
|
|
|
|
try {
|
|
|
|
//
|
|
// We will send kerberos instead of a username that we
|
|
// don't know. (unicode or char).
|
|
//
|
|
|
|
*SendData = *ReceiveData = 0;
|
|
|
|
RdrGetUserName(&Se->LogonId, &KerberosText);
|
|
|
|
if (Server->Capabilities & DF_UNICODE) {
|
|
|
|
*SendDataCount = KerberosText.Length + 4;
|
|
} else {
|
|
*SendDataCount = ( KerberosText.Length >> 1 ) + 2;
|
|
}
|
|
|
|
//
|
|
// get the blob from the kerberos package
|
|
//
|
|
|
|
Status = RdrGetKerberosBlob(Se,
|
|
&Blob,
|
|
&BlobLength,
|
|
TempPrincipal.Buffer ?
|
|
&TempPrincipal :
|
|
&Server->Principal,
|
|
pucIn,
|
|
ulIn,
|
|
TRUE);
|
|
|
|
if (!NT_SUCCESS(Status)
|
|
||
|
|
!Blob)
|
|
{
|
|
try_return(Status);
|
|
}
|
|
|
|
//
|
|
// allocate space for buffer to send via the transact2
|
|
// (blob length + maxbuffersize + maxmpxcount)
|
|
//
|
|
|
|
*SendDataCount += BlobLength + sizeof (REQ_CAIRO_TRANS2_SESSION_SETUP);
|
|
|
|
*SendData = ExAllocatePool(PagedPool, *SendDataCount);
|
|
|
|
|
|
if (*SendData == NULL ) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
try_return(Status);
|
|
}
|
|
//
|
|
// Fill in the maxbuffser size and mpxcount
|
|
//
|
|
|
|
BlobPointer = (PREQ_CAIRO_TRANS2_SESSION_SETUP) *SendData;
|
|
|
|
|
|
BlobPointer->WordCount = 6;
|
|
|
|
//
|
|
// The number of VC number field is set to the number of sessions
|
|
// outstanding on a VC.
|
|
//
|
|
|
|
BlobPointer->VcNumber = RdrGetNumberSessions(Server);
|
|
|
|
(USHORT) BlobPointer->MaxBufferSize = (USHORT) Server->BufferSize;
|
|
|
|
(USHORT) BlobPointer->MaxMpxCount = Server->MaximumRequests;
|
|
|
|
BlobPointer->Capabilities = CAP_NT_STATUS;
|
|
|
|
//
|
|
// move the blob into the buffer
|
|
//
|
|
|
|
RtlMoveMemory(BlobPointer->Buffer, Blob, BlobLength);
|
|
|
|
BlobPointer->BufferLength = BlobLength;
|
|
|
|
//
|
|
// Now actually copy the username on to the end of the buffer.
|
|
//
|
|
|
|
if (Server->Capabilities & DF_UNICODE) {
|
|
|
|
BufPtr = ALIGN_SMB_WSTR(BlobPointer->Buffer + BlobLength);
|
|
|
|
RdrCopyUnicodeStringToUnicode((PVOID *)&BufPtr, &KerberosText, TRUE);
|
|
|
|
*((PWCH)BufPtr)++ = L'\0';
|
|
|
|
} else {
|
|
|
|
BufPtr = BlobPointer->Buffer + BlobLength;
|
|
|
|
Status = RdrCopyUnicodeStringToAscii((PUCHAR *)&BufPtr, &KerberosText, TRUE, (USHORT)(*SendDataCount - BlobLength));
|
|
|
|
*((PCHAR)BufPtr)++ = '\0';
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Allocate space for the return kerberos blob. The return blob is
|
|
// going to have a kerberos reply message in addition to the rest
|
|
// of the blob.
|
|
//
|
|
|
|
*ReceiveDataCount = *SendDataCount + KERBSIZE_AP_REPLY;
|
|
|
|
*ReceiveData = ExAllocatePool(PagedPool, *ReceiveDataCount);
|
|
|
|
//
|
|
// all done.
|
|
//
|
|
|
|
if(!*ReceiveData)
|
|
{
|
|
ExFreePool(*SendData);
|
|
*SendData = 0;
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
try_return(Status = STATUS_SUCCESS);
|
|
|
|
try_exit:NOTHING;
|
|
} finally {
|
|
|
|
//
|
|
// Free the pool used for the blob if pool was allocated.
|
|
//
|
|
|
|
if (Blob != NULL) {
|
|
ExFreePool(Blob);
|
|
}
|
|
|
|
if (KerberosText.Buffer != NULL) {
|
|
FREE_POOL(KerberosText.Buffer);
|
|
}
|
|
|
|
if(TempPrincipal.Buffer)
|
|
{
|
|
FREE_POOL(TempPrincipal.Buffer);
|
|
}
|
|
}
|
|
dprintf(DPRT_CAIRO, (" -- BuildCairoConnect - done, status = %lC\n",Status));
|
|
return Status;
|
|
|
|
}
|
|
|
|
#endif // _CAIRO_
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
BuildTreeConnectAndX (
|
|
IN PSMB_BUFFER SmbBuffer,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se,
|
|
IN BOOLEAN CombiningAndX,
|
|
IN ULONG Type
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds a TreeConnect&X SMB into the supplied SMB buffer.
|
|
|
|
Arguments:
|
|
|
|
IN PSMB_HEADER Smb, - Supplies the SMB buffer to fill in with the request
|
|
IN PCONNECTLISTENTRY Connection, - Supplies the connection to connect to
|
|
IN PSECURITY_ENTRY Se, - Supplies security context for the connection
|
|
IN BOOLEAN CombiningAndX - TRUE if we are combining with another protocol
|
|
FALSE if we are building only a Tree&X
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PSMB_HEADER Smb = (PSMB_HEADER )SmbBuffer->Buffer;
|
|
PREQ_TREE_CONNECT_ANDX TreeConnect;
|
|
PVOID p;
|
|
NTSTATUS Status;
|
|
PSERVERLISTENTRY Server = Connection->Server;
|
|
|
|
PAGED_CODE();
|
|
|
|
if (CombiningAndX) {
|
|
PGENERIC_ANDX AndX = (PGENERIC_ANDX )(Smb+1);
|
|
AndX->AndXCommand = SMB_COM_TREE_CONNECT_ANDX;
|
|
TreeConnect = (PREQ_TREE_CONNECT_ANDX )((PCHAR )Smb+
|
|
SmbGetUshort(&AndX->AndXOffset));
|
|
} else {
|
|
RdrSmbScrounge(Smb, Connection->Server, FALSE, TRUE, TRUE);
|
|
Smb->Command = SMB_COM_TREE_CONNECT_ANDX;
|
|
TreeConnect = (PREQ_TREE_CONNECT_ANDX )(Smb+1);
|
|
}
|
|
|
|
TreeConnect->WordCount = 4;
|
|
TreeConnect->AndXCommand = 0xff; // No AndX
|
|
TreeConnect->AndXReserved = 0; // MBZ reserved.
|
|
SmbPutUshort(&TreeConnect->AndXOffset, 0x0); // No AndX
|
|
|
|
SmbPutUshort(&TreeConnect->Flags, 0x0); // Don't disconnect current TID.
|
|
|
|
p = (PVOID)TreeConnect->Buffer;
|
|
|
|
//
|
|
// User level security servers ignore the password in the Treeconnect&X
|
|
// SMB, so don't set it if user level security.
|
|
//
|
|
|
|
if (Connection->Server->UserSecurity) {
|
|
SmbPutUshort(&TreeConnect->PasswordLength,0x1); // Password length = 0
|
|
*((PCHAR)p)++ = '\0'; // Stick null in password.
|
|
} else {
|
|
NTSTATUS Status;
|
|
dprintf(DPRT_CONNECT, ("TreeConnect to share server"));
|
|
|
|
//
|
|
// If this server encrypts passwords (and is a share level security
|
|
// server), we want to call into the LSA to encrypt the password.
|
|
//
|
|
|
|
if (Connection->Server->EncryptPasswords) {
|
|
STRING EncryptedPassword;
|
|
|
|
if ((Se->Flags & SE_USE_DEFAULT_PASS)) {
|
|
|
|
//
|
|
// No explicit password. Send a single blank, null-terminated.
|
|
// If the server is not enforcing a password, it will ignore
|
|
// this. If it is a WFW server enforcing a blank readonly
|
|
// password and a nonblank read/write password, it will return
|
|
// error 2118 (duplicate share!) when it sees this. This will
|
|
// be translated to STATUS_LOGON_FAILURE, and the UI (winfile)
|
|
// will prompt for a password, at which point the user can get
|
|
// readonly access by entering an explicit empty password.
|
|
// If we didn't do this, there would be no way to get full access
|
|
// to the share via winfile.
|
|
//
|
|
|
|
SmbPutUshort(&TreeConnect->PasswordLength, 2*sizeof(CHAR));
|
|
*((PCHAR)p)++ = ' ';
|
|
*((PCHAR)p)++ = '\0';
|
|
|
|
} else {
|
|
|
|
if (Connection->Server->Capabilities & (DF_UNICODE | DF_MIXEDCASEPW)) {
|
|
Status = RdrGetChallengeResponse(Server->CryptKey, Se,
|
|
&EncryptedPassword, NULL, NULL, NULL,
|
|
TRUE); // disable default password
|
|
} else {
|
|
Status = RdrGetChallengeResponse(Server->CryptKey, Se,
|
|
NULL, &EncryptedPassword, NULL, NULL,
|
|
TRUE); // disable default password
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
return Status;
|
|
}
|
|
|
|
SmbPutUshort(&TreeConnect->PasswordLength, EncryptedPassword.Length);
|
|
|
|
RtlCopyMemory(p, EncryptedPassword.Buffer, EncryptedPassword.Length);
|
|
|
|
(PCHAR)p += EncryptedPassword.Length;
|
|
|
|
if (EncryptedPassword.Length != 0) {
|
|
FREE_POOL(EncryptedPassword.Buffer);
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Otherwise, if we have an explicit password, we want to use that
|
|
//
|
|
|
|
} else if (!(Se->Flags & SE_USE_DEFAULT_PASS)) {
|
|
|
|
if (Connection->Server->Capabilities & DF_UNICODE) {
|
|
UNICODE_STRING DestinationString;
|
|
|
|
p = ALIGN_SMB_WSTR(p);
|
|
DestinationString.Buffer = (PWSTR)p;
|
|
|
|
DestinationString.MaximumLength = (LM20_PWLEN+1)*sizeof(WCHAR);
|
|
|
|
DestinationString.Length = 0;
|
|
|
|
RtlCopyUnicodeString(&DestinationString, &Se->Password);
|
|
|
|
//
|
|
// If the server doesn't support mixed case passwords,
|
|
// upper case the password before connecting.
|
|
//
|
|
|
|
if (!(Connection->Server->Capabilities & DF_MIXEDCASEPW)) {
|
|
|
|
Status = RtlUpcaseUnicodeString(&DestinationString, &DestinationString, FALSE);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
}
|
|
|
|
//
|
|
// This server doesn't encrypt, and we have a clear text
|
|
// password for us to send, so send it.
|
|
//
|
|
|
|
SmbPutUshort(&TreeConnect->PasswordLength,
|
|
(USHORT )(DestinationString.Length+1));
|
|
|
|
(PWSTR)p+= DestinationString.Length;
|
|
|
|
*((PWSTR)p)++ = '\0';
|
|
} else {
|
|
OEM_STRING DestinationString;
|
|
UNICODE_STRING Password;
|
|
|
|
Password.Buffer = NULL;
|
|
|
|
DestinationString.Buffer = (PUCHAR)p;
|
|
|
|
DestinationString.MaximumLength = LM20_PWLEN+1;
|
|
|
|
DestinationString.Length = 0;
|
|
|
|
if (Se->Password.MaximumLength != 0) {
|
|
Password.Buffer = ALLOCATE_POOL(PagedPool, Se->Password.MaximumLength, POOL_PASSWORD);
|
|
}
|
|
|
|
Password.MaximumLength = Se->Password.MaximumLength;
|
|
|
|
//
|
|
// If the server doesn't support mixed case passwords,
|
|
// upper case the password before connecting.
|
|
//
|
|
|
|
if (!(Connection->Server->Capabilities & DF_MIXEDCASEPW)) {
|
|
RtlUpcaseUnicodeString(&Password, &Se->Password, FALSE);
|
|
} else {
|
|
RtlCopyUnicodeString(&Password, &Se->Password);
|
|
}
|
|
|
|
Status = RtlUnicodeStringToOemString(&DestinationString, &Password, FALSE);
|
|
|
|
if (Password.Buffer) {
|
|
FREE_POOL(Password.Buffer);
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
//
|
|
// This server doesn't encrypt, and we have a clear text
|
|
// password for us to send, so send it.
|
|
//
|
|
|
|
SmbPutUshort(&TreeConnect->PasswordLength,
|
|
(USHORT )(DestinationString.Length+1));
|
|
|
|
(PCHAR)p+= DestinationString.Length;
|
|
|
|
*((PCHAR)p)++ = '\0';
|
|
|
|
}
|
|
|
|
//
|
|
// Otherwise he wants us to use our logged in password, and we
|
|
// can't do that - so use NULL for the password.
|
|
//
|
|
|
|
} else {
|
|
|
|
// Must not provide unencrypted password - send a single blank
|
|
SmbPutUshort(&TreeConnect->PasswordLength,2*sizeof(CHAR));
|
|
*((PCHAR)p)++ = ' ';
|
|
*((PCHAR)p)++ = '\0';
|
|
}
|
|
}
|
|
|
|
//
|
|
// Copy the buffer into the TreeConnect&X SMB.
|
|
//
|
|
|
|
Status = RdrCanonicalizeAndCopyShare(&p, &Connection->Server->Text, &Connection->Text, Connection->Server);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
strcpy((PCHAR)p, RdrConnectTypes[Type]);
|
|
|
|
((PCHAR)p) += strlen(RdrConnectTypes[Type]);
|
|
|
|
*((PCHAR)p)++ = '\0';
|
|
|
|
SmbPutUshort(&TreeConnect->ByteCount, (USHORT )(((PCHAR)p)-TreeConnect->Buffer));
|
|
SmbBuffer->Mdl->ByteCount = (USHORT )(((PCHAR)p)-(PUCHAR)Smb);
|
|
|
|
UNREFERENCED_PARAMETER(Se);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
DBGSTATIC
|
|
NTSTATUS
|
|
TreeDisconnect (
|
|
IN PIRP Irp OPTIONAL,
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN PSECURITY_ENTRY Se OPTIONAL,
|
|
IN PSERVERLISTENTRY Server
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine builds and exchanges a Tree Disconnect&X SMB with the remote
|
|
server.
|
|
|
|
|
|
Arguments:
|
|
|
|
IN PCONNECTLISTENTRY Connection - Supplies the connection to disconnect
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Final status of disconnection
|
|
|
|
--*/
|
|
|
|
{
|
|
PSMB_BUFFER SmbBuffer;
|
|
PSMB_HEADER SmbHeader;
|
|
PREQ_TREE_DISCONNECT TreeDisconnect;
|
|
NTSTATUS Status;
|
|
PSECURITY_ENTRY SeForDisconnect = NULL;
|
|
|
|
PAGED_CODE();
|
|
|
|
if ((SmbBuffer = RdrAllocateSMBBuffer())==NULL) {
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
SmbHeader = (PSMB_HEADER )SmbBuffer->Buffer;
|
|
|
|
SmbHeader->Command = SMB_COM_TREE_DISCONNECT;
|
|
|
|
TreeDisconnect = (PREQ_TREE_DISCONNECT )(SmbHeader+1);
|
|
|
|
TreeDisconnect->WordCount = 0;
|
|
|
|
SmbPutUshort(&TreeDisconnect->ByteCount, 0);
|
|
|
|
SmbBuffer->Mdl->ByteCount = sizeof(SMB_HEADER) + 3;
|
|
|
|
if (!ARGUMENT_PRESENT(Se)) {
|
|
|
|
//
|
|
// There is no security entry provided for this request.
|
|
//
|
|
|
|
RdrLog(("td find",NULL,2,PsGetCurrentThread(),Server));
|
|
SeForDisconnect = RdrFindActiveSecurityEntry(Server,
|
|
NULL);
|
|
|
|
if (SeForDisconnect == NULL) {
|
|
|
|
RdrFreeSMBBuffer(SmbBuffer);
|
|
|
|
//
|
|
// Fail the tree disconnect if we can't find a security entry.
|
|
//
|
|
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
Status = RdrNetTranceive(NT_NORECONNECT | NT_RECONNECTING, // Flags
|
|
Irp, // Irp
|
|
Connection, // ServerListEntry
|
|
SmbBuffer->Mdl, // Send MDL
|
|
NULL, // Receive MDL.
|
|
ARGUMENT_PRESENT(Se) ? Se : SeForDisconnect); // Security entry.
|
|
|
|
RdrFreeSMBBuffer(SmbBuffer);
|
|
|
|
if (SeForDisconnect != NULL) {
|
|
RdrDereferenceSecurityEntry(SeForDisconnect->NonPagedSecurityEntry);
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
VOID
|
|
RdrEvaluateTimeouts (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine queries the transport for new delay, throughput and reliability
|
|
data. This is used to alter ReadAhead, WriteBehind and NCB timeouts. This is
|
|
primarily to cope with WAN and wireless LAN connections.
|
|
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
LONG Result;
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// We do not want to re-evaluate on each tick. Just do it every 20-30 seconds.
|
|
//
|
|
|
|
Result = InterlockedDecrement (&RdrConnectionTickCount);
|
|
|
|
if ( Result == 0 ) {
|
|
|
|
RdrConnectionTickCount = RdrRequestTimeout/(2*SCAVENGER_TIMER_GRANULARITY);
|
|
|
|
dprintf(DPRT_CONNECT, ("RdrEvaluateTimeouts.."));
|
|
|
|
RdrForeachServer(EvaluateServerTimeouts, NULL);
|
|
|
|
dprintf(DPRT_CONNECT, ("..Done\n"));
|
|
}
|
|
}
|
|
|
|
VOID
|
|
EvaluateServerTimeouts(
|
|
IN PSERVERLISTENTRY Server,
|
|
IN PVOID Context
|
|
)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
if (Server->ConnectionValid &&
|
|
!Server->DisconnectNeeded) {
|
|
|
|
RdrQueryConnectionInformation(Server);
|
|
|
|
}
|
|
}
|
|
|
|
VOID
|
|
RdrSetConnectlistFlag(
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN ULONG Flag
|
|
)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
KeWaitForMutexObject(&RdrDatabaseMutex, Executive, KernelMode, FALSE, NULL);
|
|
|
|
Connection->Flags |= Flag;
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
#if 0
|
|
KIRQL OldIrql;
|
|
|
|
LOCK_CONNECTION_FLAGS(OldIrql);
|
|
|
|
Connection->Flags |= Flag;
|
|
|
|
UNLOCK_CONNECTION_FLAGS(OldIrql);
|
|
#endif
|
|
}
|
|
|
|
VOID
|
|
RdrResetConnectlistFlag(
|
|
IN PCONNECTLISTENTRY Connection,
|
|
IN ULONG Flag
|
|
)
|
|
{
|
|
PAGED_CODE();
|
|
|
|
KeWaitForMutexObject(&RdrDatabaseMutex, Executive, KernelMode, FALSE, NULL);
|
|
|
|
Connection->Flags &= ~Flag;
|
|
|
|
KeReleaseMutex(&RdrDatabaseMutex, FALSE);
|
|
|
|
#if 0
|
|
KIRQL oldIrql;
|
|
|
|
LOCK_CONNECTION_FLAGS(oldIrql);
|
|
|
|
Connection->Flags &= ~Flag;
|
|
|
|
UNLOCK_CONNECTION_FLAGS(oldIrql);
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
//
|
|
// Initialization routines
|
|
//
|
|
//
|
|
|
|
VOID
|
|
RdrpInitializeConnectPackage (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the structures used by the connection package
|
|
.
|
|
Arguments:
|
|
|
|
None
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Initialize the global connectlist mutex
|
|
//
|
|
|
|
#ifndef _IDWBUILD
|
|
ULONG i;
|
|
|
|
DbgPrint("WARNING: This NT Build cannot access the following network servers:\n");
|
|
|
|
for (i = 0; i < RdrNumberOfIllegalServerNames ; i++ ) {
|
|
DbgPrint(" \\\\%ws\\%ws\n", RdrIllegalServerNames[i].ServerName, RdrIllegalServerNames[i].ShareName);
|
|
}
|
|
#endif
|
|
|
|
KeInitializeMutex (&RdrDatabaseMutex, MUTEX_LEVEL_RDR_FILESYS_DATABASE);
|
|
|
|
KeInitializeSpinLock(&RdrGlobalSleSpinLock);
|
|
|
|
KeInitializeSpinLock(&RdrServerConnectionValidSpinLock);
|
|
|
|
//
|
|
// Allocate a spin lock to guard the server list.
|
|
//
|
|
|
|
KeInitializeSpinLock(&RdrServerListSpinLock);
|
|
|
|
KeInitializeSpinLock(&RdrConnectionFlagsSpinLock);
|
|
|
|
InitializeListHead(&RdrServerHead);
|
|
|
|
InitializeListHead(&RdrServerScavengerListHead);
|
|
|
|
InitializeListHead(&RdrConnectHead);
|
|
|
|
RdrConnectionSerialNumber = 1;
|
|
|
|
//
|
|
// Maximum timezone bias.
|
|
//
|
|
|
|
RdrMaxTimezoneBias.QuadPart = Int32x32To64(24*60*60, 1000*10000);
|
|
|
|
}
|
|
|
|
#ifdef RDRDBG_REQUEST_RESOURCE
|
|
VOID
|
|
UpdateRequestResourceHistory (
|
|
PSERVERLISTENTRY Server,
|
|
BOOLEAN Acquiring,
|
|
BOOLEAN Mode,
|
|
UCHAR Number
|
|
)
|
|
{
|
|
KIRQL oldIrql;
|
|
ULONG index;
|
|
ULONG value;
|
|
|
|
KeAcquireSpinLock( &Server->RequestHistoryLock, &oldIrql );
|
|
index = Server->RequestHistoryIndex++;
|
|
index = index % 64;
|
|
if ( Acquiring ) {
|
|
value = ('a' << 24) | ((Mode ? 'e' : 's') << 16) | (('0' + Number) << 8) | ' ';
|
|
} else {
|
|
value = ('r' << 24) | ((Mode ? 'X' : ' ') << 16) | (('0' + Number) << 8) | ' ';
|
|
}
|
|
Server->RequestHistory[index] = value;
|
|
KeReleaseSpinLock( &Server->RequestHistoryLock, oldIrql );
|
|
return;
|
|
}
|
|
|
|
BOOLEAN
|
|
AcquireRequestResourceExclusive (
|
|
PSERVERLISTENTRY Server,
|
|
BOOLEAN Wait,
|
|
UCHAR Number
|
|
)
|
|
{
|
|
UpdateRequestResourceHistory( Server, TRUE, TRUE, Number );
|
|
if ( ExAcquireResourceExclusive( &Server->OutstandingRequestResource, Wait ) ) {
|
|
return TRUE;
|
|
}
|
|
UpdateRequestResourceHistory( Server, FALSE, TRUE, Number );
|
|
return FALSE;
|
|
}
|
|
|
|
BOOLEAN
|
|
AcquireRequestResourceShared (
|
|
PSERVERLISTENTRY Server,
|
|
BOOLEAN Wait,
|
|
UCHAR Number
|
|
)
|
|
{
|
|
UpdateRequestResourceHistory( Server, TRUE, FALSE, Number );
|
|
if ( ExAcquireResourceShared( &Server->OutstandingRequestResource, Wait ) ) {
|
|
return TRUE;
|
|
}
|
|
UpdateRequestResourceHistory( Server, FALSE, TRUE, Number );
|
|
return FALSE;
|
|
}
|
|
|
|
VOID
|
|
ReleaseRequestResource (
|
|
PSERVERLISTENTRY Server,
|
|
UCHAR Number
|
|
)
|
|
{
|
|
UpdateRequestResourceHistory( Server, FALSE, FALSE, Number );
|
|
ExReleaseResource( &Server->OutstandingRequestResource );
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
ReleaseRequestResourceForThread (
|
|
PSERVERLISTENTRY Server,
|
|
ERESOURCE_THREAD Thread,
|
|
UCHAR Number
|
|
)
|
|
{
|
|
UpdateRequestResourceHistory( Server, FALSE, FALSE, Number );
|
|
ExReleaseResourceForThread( &Server->OutstandingRequestResource, Thread );
|
|
return;
|
|
}
|
|
#endif
|
|
|