summaryrefslogtreecommitdiffstats
path: root/private/nw/rdr/attach.c
diff options
context:
space:
mode:
Diffstat (limited to 'private/nw/rdr/attach.c')
-rw-r--r--private/nw/rdr/attach.c5169
1 files changed, 5169 insertions, 0 deletions
diff --git a/private/nw/rdr/attach.c b/private/nw/rdr/attach.c
new file mode 100644
index 000000000..099cfc3d1
--- /dev/null
+++ b/private/nw/rdr/attach.c
@@ -0,0 +1,5169 @@
+/*++
+
+Copyright (c) 1992 Microsoft Corporation
+
+Module Name:
+
+ Attach.c
+
+Abstract:
+
+ This module implements the routines for the NetWare
+ redirector to connect and disconnect from a server.
+
+Author:
+
+ Colin Watson [ColinW] 10-Jan-1992
+
+Revision History:
+
+--*/
+
+#include "Procs.h"
+#include <stdlib.h> // rand
+
+//
+// The number of bytes in the ipx host address, not
+// including the socket.
+//
+
+#define IPX_HOST_ADDR_LEN 10
+
+//
+// The debug trace level
+//
+
+#define Dbg (DEBUG_TRACE_CREATE)
+
+VOID
+ExtractNextComponentName (
+ OUT PUNICODE_STRING Name,
+ IN PUNICODE_STRING Path,
+ IN BOOLEAN ColonSeparator
+ );
+
+NTSTATUS
+ExtractPathAndFileName(
+ IN PUNICODE_STRING EntryPath,
+ OUT PUNICODE_STRING PathString,
+ OUT PUNICODE_STRING FileName
+ );
+
+NTSTATUS
+DoBinderyLogon(
+ IN PIRP_CONTEXT pIrpContext,
+ IN PUNICODE_STRING UserName,
+ IN PUNICODE_STRING Password
+ );
+
+NTSTATUS
+ConnectToServer(
+ IN PIRP_CONTEXT pIrpContext,
+ OUT PSCB *pScbCollision
+ );
+
+BOOLEAN
+ProcessFindNearestEntry(
+ PIRP_CONTEXT IrpContext,
+ PSAP_FIND_NEAREST_RESPONSE FindNearestResponse
+ );
+
+NTSTATUS
+GetMaxPacketSize(
+ PIRP_CONTEXT pIrpContext,
+ PNONPAGED_SCB pNpScb
+ );
+
+PNONPAGED_SCB
+FindServer(
+ PIRP_CONTEXT pIrpContext,
+ PNONPAGED_SCB pNpScb,
+ PUNICODE_STRING ServerName
+ );
+
+NTSTATUS
+NwAllocateAndInitScb(
+ IN PIRP_CONTEXT pIrpContext,
+ IN PUNICODE_STRING UidServerName OPTIONAL,
+ IN PUNICODE_STRING ServerName OPTIONAL,
+ OUT PSCB *ppScb
+);
+
+#ifdef ALLOC_PRAGMA
+#pragma alloc_text( PAGE, ExtractNextComponentName )
+#pragma alloc_text( PAGE, ExtractPathAndFileName )
+#pragma alloc_text( PAGE, CrackPath )
+#pragma alloc_text( PAGE, CreateScb )
+#pragma alloc_text( PAGE, FindServer )
+#pragma alloc_text( PAGE, ProcessFindNearestEntry )
+#pragma alloc_text( PAGE, NegotiateBurstMode )
+#pragma alloc_text( PAGE, GetMaxPacketSize )
+#pragma alloc_text( PAGE, NwDeleteScb )
+#pragma alloc_text( PAGE, NwLogoffAndDisconnect )
+#pragma alloc_text( PAGE, InitializeAttach )
+#pragma alloc_text( PAGE, OpenScbSockets )
+#pragma alloc_text( PAGE, DoBinderyLogon )
+#pragma alloc_text( PAGE, QueryServersAddress )
+#pragma alloc_text( PAGE, TreeConnectScb )
+#pragma alloc_text( PAGE, TreeDisconnectScb )
+
+#ifndef QFE_BUILD
+#pragma alloc_text( PAGE1, ProcessFindNearest )
+#pragma alloc_text( PAGE1, NwLogoffAllServers )
+#pragma alloc_text( PAGE1, DestroyAllScb )
+#pragma alloc_text( PAGE1, SelectConnection )
+#pragma alloc_text( PAGE1, NwFindScb )
+#pragma alloc_text( PAGE1, ConnectToServer )
+#endif
+
+#endif
+
+#if 0 // Not pageable
+
+// see ifndef QFE_BUILD above
+
+#endif
+
+
+VOID
+ExtractNextComponentName (
+ OUT PUNICODE_STRING Name,
+ IN PUNICODE_STRING Path,
+ IN BOOLEAN ColonSeparator
+ )
+
+/*++
+
+Routine Description:
+
+ This routine extracts a the "next" component from a path string.
+
+ It assumes that
+
+Arguments:
+
+ Name - Returns a pointer to the component.
+
+ Path - Supplies a pointer to the backslash seperated pathname.
+
+ ColonSeparator - A colon can be used to terminate this component
+ name.
+
+Return Value:
+
+ None
+
+--*/
+
+{
+ register USHORT i; // Index into Name string.
+
+ PAGED_CODE();
+
+ if (Path->Length == 0) {
+ RtlInitUnicodeString(Name, NULL);
+ return;
+ }
+
+ //
+ // Initialize the extracted name to the name passed in skipping the
+ // leading backslash.
+ //
+
+ // DebugTrace(+0, Dbg, "NwExtractNextComponentName = %wZ\n", Path );
+
+ Name->Buffer = Path->Buffer + 1;
+ Name->Length = Path->Length - sizeof(WCHAR);
+ Name->MaximumLength = Path->MaximumLength - sizeof(WCHAR);
+
+ //
+ // Scan forward finding the terminal "\" in the server name.
+ //
+
+ for (i=0;i<(USHORT)(Name->Length/sizeof(WCHAR));i++) {
+
+ if ( Name->Buffer[i] == OBJ_NAME_PATH_SEPARATOR ||
+ ( ColonSeparator && Name->Buffer[i] == L':' ) ) {
+ break;
+ }
+ }
+
+ //
+ // Update the length and maximum length of the structure
+ // to match the new length.
+ //
+
+ Name->Length = Name->MaximumLength = (USHORT)(i*sizeof(WCHAR));
+}
+
+
+NTSTATUS
+ExtractPathAndFileName (
+ IN PUNICODE_STRING EntryPath,
+ OUT PUNICODE_STRING PathString,
+ OUT PUNICODE_STRING FileName
+ )
+/*++
+
+Routine Description:
+
+ This routine cracks the entry path into two pieces, the path and the file
+name component at the start of the name.
+
+
+Arguments:
+
+ IN PUNICODE_STRING EntryPath - Supplies the path to disect.
+ OUT PUNICODE_STRING PathString - Returns the directory containing the file.
+ OUT PUNICODE_STRING FileName - Returns the file name specified.
+
+Return Value:
+
+ NTSTATUS - SUCCESS
+
+
+--*/
+
+{
+ UNICODE_STRING Component;
+ UNICODE_STRING FilePath = *EntryPath;
+
+ PAGED_CODE();
+
+ // Strip trailing separators
+ while ( (FilePath.Length != 0) &&
+ FilePath.Buffer[(FilePath.Length-1)/sizeof(WCHAR)] ==
+ OBJ_NAME_PATH_SEPARATOR ) {
+
+ FilePath.Length -= sizeof(WCHAR);
+ FilePath.MaximumLength -= sizeof(WCHAR);
+ }
+
+ // PathString will become EntryPath minus FileName and trailing separators
+ *PathString = FilePath;
+
+ // Initialize FileName just incase there are no components at all.
+ RtlInitUnicodeString( FileName, NULL );
+
+ //
+ // Scan through the current file name to find the entire path
+ // up to (but not including) the last component in the path.
+ //
+
+ do {
+
+ //
+ // Extract the next component from the name.
+ //
+
+ ExtractNextComponentName(&Component, &FilePath, FALSE);
+
+ //
+ // Bump the "remaining name" pointer by the length of this
+ // component
+ //
+
+ if (Component.Length != 0) {
+
+ FilePath.Length -= Component.Length+sizeof(WCHAR);
+ FilePath.MaximumLength -= Component.MaximumLength+sizeof(WCHAR);
+ FilePath.Buffer += (Component.Length/sizeof(WCHAR))+1;
+
+ *FileName = Component;
+ }
+
+
+ } while (Component.Length != 0);
+
+ //
+ // Take the name, subtract the last component of the name
+ // and concatenate the current path with the new path.
+ //
+
+ if ( FileName->Length != 0 ) {
+
+ //
+ // Set the path's name based on the original name, subtracting
+ // the length of the name portion (including the "\")
+ //
+
+ PathString->Length -= (FileName->Length + sizeof(WCHAR));
+ if ( PathString->Length != 0 ) {
+ PathString->MaximumLength -= (FileName->MaximumLength + sizeof(WCHAR));
+ } else{
+ RtlInitUnicodeString( PathString, NULL );
+ }
+ } else {
+
+ // There was no path or filename
+
+ RtlInitUnicodeString( PathString, NULL );
+ }
+
+ return STATUS_SUCCESS;
+}
+
+
+NTSTATUS
+CrackPath (
+ IN PUNICODE_STRING BaseName,
+ OUT PUNICODE_STRING DriveName,
+ OUT PWCHAR DriveLetter,
+ OUT PUNICODE_STRING ServerName,
+ OUT PUNICODE_STRING VolumeName,
+ OUT PUNICODE_STRING PathName,
+ OUT PUNICODE_STRING FileName,
+ OUT PUNICODE_STRING FullName OPTIONAL
+ )
+
+/*++
+
+Routine Description:
+
+ This routine extracts the relevant portions from BaseName to extract
+ the components of the user's string.
+
+
+Arguments:
+
+ BaseName - Supplies the base user's path.
+
+ DriveName - Supplies a string to hold the drive specifier.
+
+ DriveLetter - Returns the drive letter. 0 for none, 'A'-'Z' for
+ disk drives, '1'-'9' for LPT connections.
+
+ ServerName - Supplies a string to hold the remote server name.
+
+ VolumeName - Supplies a string to hold the volume name.
+
+ PathName - Supplies a string to hold the remaining part of the path.
+
+ FileName - Supplies a string to hold the final component of the path.
+
+ FullName - Supplies a string to put the Path followed by FileName
+
+Return Value:
+
+ NTSTATUS - Status of operation
+
+
+--*/
+
+{
+ NTSTATUS Status;
+
+ UNICODE_STRING BaseCopy = *BaseName;
+ UNICODE_STRING ShareName;
+
+ PAGED_CODE();
+
+ RtlInitUnicodeString( DriveName, NULL);
+ RtlInitUnicodeString( ServerName, NULL);
+ RtlInitUnicodeString( VolumeName, NULL);
+ RtlInitUnicodeString( PathName, NULL);
+ RtlInitUnicodeString( FileName, NULL);
+ *DriveLetter = 0;
+
+ if (ARGUMENT_PRESENT(FullName)) {
+ RtlInitUnicodeString( FullName, NULL);
+ }
+
+ //
+ // If the name is "\", or empty, there is nothing to do.
+ //
+
+ if ( BaseName->Length <= sizeof( WCHAR ) ) {
+ return STATUS_SUCCESS;
+ }
+
+ ExtractNextComponentName(ServerName, &BaseCopy, FALSE);
+
+ //
+ // Skip over the server name.
+ //
+
+ BaseCopy.Buffer += (ServerName->Length / sizeof(WCHAR)) + 1;
+ BaseCopy.Length -= ServerName->Length + sizeof(WCHAR);
+ BaseCopy.MaximumLength -= ServerName->MaximumLength + sizeof(WCHAR);
+
+ if ((ServerName->Length == sizeof(L"X:") - sizeof(WCHAR) ) &&
+ (ServerName->Buffer[(ServerName->Length / sizeof(WCHAR)) - 1] == L':'))
+ {
+
+ //
+ // The file name is of the form x:\server\volume\foo\bar
+ //
+
+ *DriveName = *ServerName;
+ *DriveLetter = DriveName->Buffer[0];
+
+ RtlInitUnicodeString( ServerName, NULL );
+ ExtractNextComponentName(ServerName, &BaseCopy, FALSE);
+
+ if ( ServerName->Length != 0 ) {
+
+ //
+ // Skip over the server name.
+ //
+
+ BaseCopy.Buffer += (ServerName->Length / sizeof(WCHAR)) + 1;
+ BaseCopy.Length -= ServerName->Length + sizeof(WCHAR);
+ BaseCopy.MaximumLength -= ServerName->MaximumLength + sizeof(WCHAR);
+ }
+ }
+ else if ( ( ServerName->Length == sizeof(L"LPTx") - sizeof(WCHAR) ) &&
+ ( _wcsnicmp( ServerName->Buffer, L"LPT", 3 ) == 0) &&
+ ( ServerName->Buffer[3] >= '0' && ServerName->Buffer[3] <= '9' ) )
+ {
+
+ //
+ // The file name is of the form LPTx\server\printq
+ //
+
+ *DriveName = *ServerName;
+ *DriveLetter = DriveName->Buffer[3];
+
+ RtlInitUnicodeString( ServerName, NULL );
+ ExtractNextComponentName(ServerName, &BaseCopy, FALSE);
+
+ if ( ServerName->Length != 0 ) {
+
+ //
+ // Skip over the server name.
+ //
+
+ BaseCopy.Buffer += (ServerName->Length / sizeof(WCHAR)) + 1;
+ BaseCopy.Length -= ServerName->Length + sizeof(WCHAR);
+ BaseCopy.MaximumLength -= ServerName->MaximumLength + sizeof(WCHAR);
+ }
+ }
+
+ if ( ServerName->Length != 0 ) {
+
+ //
+ // The file name is of the form \\server\volume\foo\bar
+ // Set volume name to server\volume.
+ //
+
+ ExtractNextComponentName( &ShareName, &BaseCopy, TRUE );
+
+ //
+ // Set volume name = \drive:\server\share or \server\share if the
+ // path is UNC.
+ //
+
+ VolumeName->Buffer = ServerName->Buffer - 1;
+
+ if ( ShareName.Length != 0 ) {
+
+ VolumeName->Length = ServerName->Length + ShareName.Length + 2 * sizeof( WCHAR );
+
+ if ( DriveName->Buffer != NULL ) {
+ VolumeName->Buffer = DriveName->Buffer - 1;
+ VolumeName->Length += DriveName->Length + sizeof(WCHAR);
+ }
+
+ BaseCopy.Buffer += ShareName.Length / sizeof(WCHAR) + 1;
+ BaseCopy.Length -= ShareName.Length + sizeof(WCHAR);
+ BaseCopy.MaximumLength -= ShareName.MaximumLength + sizeof(WCHAR);
+
+ } else {
+
+ VolumeName->Length = ServerName->Length + sizeof( WCHAR );
+ return( STATUS_SUCCESS );
+
+ }
+
+ VolumeName->MaximumLength = VolumeName->Length;
+ }
+ else
+ {
+ //
+ // server name is empty. this should only happen if we are
+ // opening the redirector itself. if there is volume or other
+ // components left, fail it.
+ //
+
+ if (BaseCopy.Length > sizeof(WCHAR))
+ {
+ return STATUS_BAD_NETWORK_PATH ;
+ }
+ }
+
+ Status = ExtractPathAndFileName ( &BaseCopy, PathName, FileName );
+
+ if (NT_SUCCESS(Status) &&
+ ARGUMENT_PRESENT(FullName)) {
+
+ //
+ // Use the feature that PathName and FileName are in the same buffer
+ // to return <pathname>\<filename>
+ //
+
+ if ( PathName->Buffer == NULL ) {
+
+ // return just <filename> or NULL
+
+ *FullName = *FileName;
+
+ } else {
+ // Set FullFileName to <PathName>'\'<FileName>
+
+ FullName->Buffer = PathName->Buffer;
+
+ FullName->Length = PathName->Length +
+ FileName->Length +
+ sizeof(WCHAR);
+
+ FullName->MaximumLength = PathName->MaximumLength +
+ FileName->MaximumLength +
+ sizeof(WCHAR);
+ }
+ }
+
+ return( Status );
+}
+
+NTSTATUS
+GetServerByAddress(
+ IN PIRP_CONTEXT pIrpContext,
+ OUT PSCB *Scb,
+ IN IPXaddress *pServerAddress
+)
+/*+++
+
+Description:
+
+ This routine looks up a server by address. If it finds a server that
+ has been connected, it returns it referenced. Otherwise, it returns no
+ server.
+
+---*/
+{
+
+ NTSTATUS Status;
+ PLIST_ENTRY ScbQueueEntry;
+ KIRQL OldIrql;
+ PNONPAGED_SCB pFirstNpScb, pNextNpScb;
+ PNONPAGED_SCB pFoundNpScb = NULL;
+ UNICODE_STRING CredentialName;
+
+ //
+ // Start at the head of the SCB list.
+ //
+
+ KeAcquireSpinLock( &ScbSpinLock, &OldIrql );
+
+ if ( ScbQueue.Flink == &ScbQueue ) {
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql);
+ return STATUS_UNSUCCESSFUL;
+ }
+
+ ScbQueueEntry = ScbQueue.Flink;
+ pFirstNpScb = CONTAINING_RECORD( ScbQueueEntry,
+ NONPAGED_SCB,
+ ScbLinks );
+ pNextNpScb = pFirstNpScb;
+
+ //
+ // Leave the first SCB referenced since we need it to
+ // be there for when we walk all the way around the list.
+ //
+
+ NwReferenceScb( pFirstNpScb );
+ NwReferenceScb( pNextNpScb );
+
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql);
+
+ while ( TRUE ) {
+
+ //
+ // Check to see if the SCB address matches the address we have
+ // and if the user uid matches the uid for this request. Skip
+ // matches that are abandoned anonymous creates.
+ //
+
+ if ( pNextNpScb->pScb ) {
+
+ if ( ( RtlCompareMemory( (BYTE *) pServerAddress,
+ (BYTE *) &pNextNpScb->ServerAddress,
+ IPX_HOST_ADDR_LEN ) == IPX_HOST_ADDR_LEN ) &&
+ ( pIrpContext->Specific.Create.UserUid.QuadPart ==
+ pNextNpScb->pScb->UserUid.QuadPart ) &&
+ ( pNextNpScb->State != SCB_STATE_FLAG_SHUTDOWN ) ) {
+
+ if ( pIrpContext->Specific.Create.fExCredentialCreate ) {
+
+ //
+ // On a credential create, the credential supplied has
+ // to match the extended credential for the server.
+ //
+
+ Status = GetCredentialFromServerName( &pNextNpScb->ServerName,
+ &CredentialName );
+ if ( !NT_SUCCESS( Status ) ) {
+ goto ContinueLoop;
+ }
+
+ if ( RtlCompareUnicodeString( &CredentialName,
+ pIrpContext->Specific.Create.puCredentialName,
+ TRUE ) ) {
+ goto ContinueLoop;
+ }
+
+ }
+
+ pFoundNpScb = pNextNpScb;
+ DebugTrace( 0, Dbg, "GetServerByAddress: %wZ\n", &pFoundNpScb->ServerName );
+ break;
+
+ }
+ }
+
+ContinueLoop:
+
+ //
+ // Otherwise, get the next one in the list. Don't
+ // forget to skip the list head.
+ //
+
+ KeAcquireSpinLock( &ScbSpinLock, &OldIrql );
+
+ ScbQueueEntry = pNextNpScb->ScbLinks.Flink;
+
+ if ( ScbQueueEntry == &ScbQueue ) {
+ ScbQueueEntry = ScbQueue.Flink;
+ }
+
+ NwDereferenceScb( pNextNpScb );
+ pNextNpScb = CONTAINING_RECORD( ScbQueueEntry, NONPAGED_SCB, ScbLinks );
+
+ if ( pNextNpScb == pFirstNpScb ) {
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql );
+ break;
+ }
+
+ //
+ // Otherwise, reference this SCB and continue.
+ //
+
+ NwReferenceScb( pNextNpScb );
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql );
+
+ }
+
+ NwDereferenceScb( pFirstNpScb );
+
+ if ( pFoundNpScb ) {
+ *Scb = pFoundNpScb->pScb;
+ return STATUS_SUCCESS;
+ }
+
+ return STATUS_UNSUCCESSFUL;
+
+}
+
+NTSTATUS
+CheckScbSecurity(
+ IN PIRP_CONTEXT pIrpContext,
+ IN PSCB pScb,
+ IN PUNICODE_STRING puUserName,
+ IN PUNICODE_STRING puPassword,
+ IN BOOLEAN fDeferLogon
+)
+/*+++
+
+ You must be at the head of the queue to call this function.
+ This function makes sure that the Scb is valid for the user
+ that requested it.
+
+---*/
+{
+
+ NTSTATUS Status;
+ BOOLEAN SecurityConflict = FALSE;
+
+ ASSERT( pScb->pNpScb->State == SCB_STATE_IN_USE );
+
+ //
+ // If there's no user name or password, there's no conflict.
+ //
+
+ if ( ( puUserName == NULL ) &&
+ ( puPassword == NULL ) ) {
+
+ return STATUS_SUCCESS;
+ }
+
+ if ( pScb->UserName.Length &&
+ pScb->UserName.Buffer ) {
+
+ //
+ // Do a bindery security check if we were bindery
+ // authenticated to this server.
+ //
+
+ if ( !fDeferLogon &&
+ puUserName != NULL &&
+ puUserName->Buffer != NULL ) {
+
+ ASSERT( pScb->Password.Buffer != NULL );
+
+ if ( !RtlEqualUnicodeString( &pScb->UserName, puUserName, TRUE ) ||
+ ( puPassword &&
+ puPassword->Buffer &&
+ puPassword->Length &&
+ !RtlEqualUnicodeString( &pScb->Password, puPassword, TRUE ) )) {
+
+ SecurityConflict = TRUE;
+
+ }
+ }
+
+ } else {
+
+ //
+ // Do an nds security check.
+ //
+
+ Status = NdsCheckCredentials( pIrpContext,
+ puUserName,
+ puPassword );
+
+ if ( !NT_SUCCESS( Status )) {
+
+ SecurityConflict = TRUE;
+ }
+
+ }
+
+ //
+ // If there was a security conflict, see if we can just
+ // take this connection over (i.e. there are no open
+ // files or open handles to the server).
+ //
+
+ if ( SecurityConflict ) {
+
+ if ( ( pScb->OpenFileCount == 0 ) &&
+ ( pScb->IcbCount == 0 ) ) {
+
+ if ( pScb->UserName.Buffer ) {
+ FREE_POOL( pScb->UserName.Buffer );
+ }
+
+ RtlInitUnicodeString( &pScb->UserName, NULL );
+ RtlInitUnicodeString( &pScb->Password, NULL );
+ pScb->pNpScb->State = SCB_STATE_LOGIN_REQUIRED;
+
+ } else {
+
+ DebugTrace( 0, Dbg, "SCB security conflict.\n", 0 );
+ return STATUS_NETWORK_CREDENTIAL_CONFLICT;
+
+ }
+
+ }
+
+ DebugTrace( 0, Dbg, "SCB security check succeeded.\n", 0 );
+ return STATUS_SUCCESS;
+
+}
+
+NTSTATUS
+GetScb(
+ OUT PSCB *Scb,
+ IN PIRP_CONTEXT pIrpContext,
+ IN PUNICODE_STRING Server,
+ IN IPXaddress *pServerAddress,
+ IN PUNICODE_STRING UserName,
+ IN PUNICODE_STRING Password,
+ IN BOOLEAN DeferLogon,
+ OUT PBOOLEAN Existing
+)
+/*+++
+
+Description:
+
+ This routine locates an existing SCB or creates a new SCB.
+ This is the first half of the original CreateScb routine.
+
+Locks:
+
+ See the anonymous create information in CreateScb().
+
+---*/
+{
+
+ NTSTATUS Status;
+ PSCB pScb = NULL;
+ PNONPAGED_SCB pNpScb = NULL;
+ BOOLEAN ExistingScb = TRUE;
+ UNICODE_STRING UidServer;
+ UNICODE_STRING ExCredName;
+ PUNICODE_STRING puConnectName;
+ KIRQL OldIrql;
+
+ DebugTrace( 0, Dbg, "GetScb... %wZ\n", Server );
+
+ if ( pServerAddress != NULL ) {
+ DebugTrace( 0, Dbg, " ->Server Address = (provided)\n", 0 );
+ } else {
+ DebugTrace( 0, Dbg, " ->Server Address = NULL\n", 0 );
+ }
+
+ RtlInitUnicodeString( &UidServer, NULL );
+
+ if ( ( Server == NULL ) ||
+ ( Server->Length == 0 ) ) {
+
+ //
+ // No server name was provided. Either this is a connect by address,
+ // or a connect to a nearby bindery server (defaulting to the preferred
+ // server).
+ //
+
+ if ( pServerAddress == NULL ) {
+
+ //
+ // No server address was provided, so this is an attempt to open
+ // a nearby bindery server.
+ //
+
+ while (TRUE) {
+
+ //
+ // The loop checks that after we get to the front, the SCB
+ // is still in the state we wanted. If not, we need to
+ // reselect another.
+ //
+
+ pNpScb = SelectConnection( NULL );
+
+ //
+ // Note: We'd like to call SelectConnection with the pNpScb
+ // that we last tried, but if the scavenger runs before
+ // this loop gets back to the select connection, we could
+ // pass a bum pointer to SelectConnection, which is bad.
+ //
+
+ if ( pNpScb != NULL) {
+
+ pScb = pNpScb->pScb;
+
+ //
+ // Queue ourselves to the SCB, wait to get to the front to
+ // protect access to server State.
+ //
+
+ pIrpContext->pNpScb = pNpScb;
+ pIrpContext->pScb = pScb;
+
+ NwAppendToQueueAndWait( pIrpContext );
+
+ //
+ // These states have to match the conditions of the
+ // SelectConnection to prevent an infinite loop.
+ //
+
+ if (!((pNpScb->State == SCB_STATE_RECONNECT_REQUIRED ) ||
+ (pNpScb->State == SCB_STATE_LOGIN_REQUIRED ) ||
+ (pNpScb->State == SCB_STATE_IN_USE ))) {
+
+ //
+ // No good any more as default server, select another.
+ //
+
+ pScb = NULL ;
+ NwDereferenceScb( pNpScb );
+ NwDequeueIrpContext( pIrpContext, FALSE );
+ continue ;
+
+ }
+ }
+
+ //
+ // otherwise, we're done
+ //
+
+ break ;
+
+ }
+
+ } else {
+
+ //
+ // An address was provided, so we are attempting to do a lookup
+ // based on address. The server that we are looking for might
+ // exist but not yet have its address recorded, so if we do an
+ // anonymous create, we have to check at the end whether or not
+ // someone else came in and successfully created while we were
+ // looking up the name.
+ //
+ // We don't have to hold the RCB anymore since colliding creates
+ // have to be handled gracefully anyway.
+ //
+
+ Status = GetServerByAddress( pIrpContext, &pScb, pServerAddress );
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ //
+ // No anonymous creates are allowed if we are not allowed
+ // to send packets to the net (because it's not possible for
+ // us to resolve the address to a name).
+ //
+
+ if ( BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_NOCONNECT ) ) {
+ return STATUS_BAD_NETWORK_PATH;
+ }
+
+ //
+ // There's no connection to this server, so we'll
+ // have to create one. Let's start with an anonymous
+ // Scb.
+ //
+
+ Status = NwAllocateAndInitScb( pIrpContext,
+ NULL,
+ NULL,
+ &pScb );
+
+ if ( !NT_SUCCESS( Status )) {
+ return Status;
+ }
+
+ //
+ // We've made the anonymous create, so put it on the scb
+ // list and get to the head of the queue.
+ //
+
+ SetFlag( pIrpContext->Flags, IRP_FLAG_ON_SCB_QUEUE );
+
+ pIrpContext->pScb = pScb;
+ pIrpContext->pNpScb = pScb->pNpScb;
+
+ ExInterlockedInsertHeadList( &pScb->pNpScb->Requests,
+ &pIrpContext->NextRequest,
+ &pScb->pNpScb->NpScbSpinLock );
+
+ KeAcquireSpinLock(&ScbSpinLock, &OldIrql);
+ InsertTailList(&ScbQueue, &pScb->pNpScb->ScbLinks);
+ KeReleaseSpinLock(&ScbSpinLock, OldIrql);
+
+ DebugTrace( 0, Dbg, "GetScb started an anonymous create.\n", 0 );
+ ExistingScb = FALSE;
+
+ } else {
+
+ //
+ // Get to the head of the queue and see if this was
+ // an abandoned anonymous create. If so, get the
+ // right server and continue.
+ //
+
+ pIrpContext->pScb = pScb;
+ pIrpContext->pNpScb = pScb->pNpScb;
+ NwAppendToQueueAndWait( pIrpContext );
+
+ if ( pScb->pNpScb->State == SCB_STATE_FLAG_SHUTDOWN ) {
+
+ //
+ // The create abandoned this scb, redoing a
+ // GetServerByAddress() is guaranteed to get
+ // us a good server if there is a server out
+ // there.
+ //
+
+ NwDequeueIrpContext( pIrpContext, FALSE );
+ NwDereferenceScb( pScb->pNpScb );
+
+ Status = GetServerByAddress( pIrpContext, &pScb, pServerAddress );
+
+ if ( NT_SUCCESS( Status ) ) {
+ ASSERT( pScb != NULL );
+ ASSERT( !IS_ANONYMOUS_SCB( pScb ) );
+ }
+
+ } else {
+
+ ASSERT( !IS_ANONYMOUS_SCB( pScb ) );
+ }
+ }
+
+ ASSERT( pScb != NULL );
+ }
+
+ } else {
+
+ //
+ // A server name was provided, so we are doing a straight
+ // lookup or create by name. Do we need to munge the name
+ // for a supplemental credential connect?
+ //
+
+ RtlInitUnicodeString( &ExCredName, NULL );
+
+ if ( ( pIrpContext->Specific.Create.fExCredentialCreate ) &&
+ ( !IsCredentialName( Server ) ) ) {
+
+ Status = BuildExCredentialServerName( Server,
+ pIrpContext->Specific.Create.puCredentialName,
+ &ExCredName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ return Status;
+ }
+
+ puConnectName = &ExCredName;
+
+ } else {
+
+ puConnectName = Server;
+ }
+
+ Status = MakeUidServer( &UidServer,
+ &pIrpContext->Specific.Create.UserUid,
+ puConnectName );
+
+
+ if ( ExCredName.Buffer ) {
+ FREE_POOL( ExCredName.Buffer );
+ }
+
+ if (!NT_SUCCESS(Status)) {
+ return Status;
+ }
+
+ DebugTrace( 0, Dbg, " ->UidServer = %wZ\n", &UidServer );
+
+ ExistingScb = NwFindScb( &pScb, pIrpContext, &UidServer, Server );
+
+ ASSERT( pScb != NULL );
+ pNpScb = pScb->pNpScb;
+
+ pIrpContext->pNpScb = pNpScb;
+ pIrpContext->pScb = pScb;
+ NwAppendToQueueAndWait(pIrpContext);
+
+ if ( ExistingScb ) {
+
+ //
+ // We found an existing SCB. If we are logged into this
+ // server make sure the supplied username and password, match
+ // the username and password that we logged in with.
+ //
+
+ if ( pNpScb->State == SCB_STATE_IN_USE ) {
+
+ Status = CheckScbSecurity( pIrpContext,
+ pScb,
+ UserName,
+ Password,
+ DeferLogon );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ FREE_POOL( UidServer.Buffer );
+ UidServer.Buffer = NULL;
+ NwDereferenceScb( pNpScb );
+ return Status;
+ }
+ }
+ }
+
+ }
+
+ //
+ // 1) We may or may not have a server (evidenced by pScb).
+ //
+ // 2) If we have a server and ExistingScb is TRUE, we have
+ // an existing server, possibly already connected.
+ // Otherwise, we have a newly created server that
+ // may or may not be anonymous.
+ //
+
+ *Scb = pScb;
+ *Existing = ExistingScb;
+
+#ifdef NWDBG
+
+ if ( pScb != NULL ) {
+
+ //
+ // If we have a server, the SCB is referenced and we will
+ // be at the head of the queue.
+ //
+
+ ASSERT( pIrpContext->pNpScb->Requests.Flink == &pIrpContext->NextRequest );
+
+ }
+
+#endif
+
+ if ( UidServer.Buffer != NULL ) {
+ FREE_POOL( UidServer.Buffer );
+ }
+
+ DebugTrace( 0, Dbg, "GetScb returned %08lx\n", pScb );
+ return STATUS_SUCCESS;
+
+}
+
+NTSTATUS
+ConnectScb(
+ IN PSCB *Scb,
+ IN PIRP_CONTEXT pIrpContext,
+ IN PUNICODE_STRING Server,
+ IN IPXaddress *pServerAddress,
+ IN PUNICODE_STRING UserName,
+ IN PUNICODE_STRING Password,
+ IN BOOLEAN DeferLogon,
+ IN BOOLEAN DeleteConnection,
+ IN BOOLEAN ExistingScb
+)
+/*+++
+
+Description:
+
+ This routine puts the provided scb in the connected state.
+ This is the second half of the original CreateScb routine.
+
+Arguments:
+
+ Scb - The scb for the server we want to connect.
+ pIrpContext - The context for this request.
+ Server - The name of the server, or NULL.
+ pServerAddress - The address of the server, or NULL,
+ UserName - The name of the user to connect as, or NULL.
+ Password - The password for the user, or NULL.
+ DeferLogon - Should we defer the logon?
+ DeleteConnection - Should we succeed even without the net so that
+ the delete request will succeed?
+ ExistingScb - Is this an existing SCB?
+
+ If the SCB is anonymous, we need to safely check for colliding
+ creates when we find out who the server is.
+
+ If this is a reconnect attempt, this routine will not dequeue the
+ irp context, which could cause a deadlock in the reconnect logic.
+
+---*/
+{
+
+ NTSTATUS Status = STATUS_SUCCESS;
+
+ PSCB pScb = *Scb;
+ PNONPAGED_SCB pNpScb = NULL;
+
+ BOOLEAN AnonymousScb = FALSE;
+ PSCB pCollisionScb = NULL;
+
+ NTSTATUS LoginStatus;
+ BOOLEAN TriedNdsLogin;
+
+ PLOGON pLogon;
+ BOOLEAN DeferredLogon = DeferLogon;
+ PNDS_SECURITY_CONTEXT pNdsContext;
+ NTSTATUS CredStatus;
+
+ DebugTrace( 0, Dbg, "ConnectScb... %08lx\n", pScb );
+
+ //
+ // If we already have an SCB, find out where in the
+ // connect chain we need to start off.
+ //
+
+ if ( pScb ) {
+
+ pNpScb = pScb->pNpScb;
+ AnonymousScb = IS_ANONYMOUS_SCB( pScb );
+
+ if ( ExistingScb ) {
+
+ ASSERT( !AnonymousScb );
+
+ //
+ // If this SCB is in STATE_ATTACHING, we need to check
+ // the address in the SCB to make sure that it was at one
+ // point a valid server. If it wasn't, then we shouldn't
+ // honor this create because it's probably a tree create.
+ //
+
+ if ( DeleteConnection ) {
+
+ ASSERT( !BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_RECONNECT_ATTEMPT ) );
+
+ if ( ( pNpScb->State == SCB_STATE_ATTACHING ) &&
+ ( (pNpScb->ServerAddress).Socket == 0 ) ) {
+
+ Status = STATUS_BAD_NETWORK_PATH;
+ goto CleanupAndExit;
+
+ } else {
+
+ NwDequeueIrpContext( pIrpContext, FALSE );
+ return STATUS_SUCCESS;
+ }
+ }
+
+RedoConnect:
+
+ if ( pNpScb->State == SCB_STATE_ATTACHING ) {
+ goto GetAddress;
+ } else if ( pNpScb->State == SCB_STATE_RECONNECT_REQUIRED ) {
+ goto Connect;
+ } else if ( pNpScb->State == SCB_STATE_LOGIN_REQUIRED ) {
+ goto Login;
+ } else if ( pNpScb->State == SCB_STATE_IN_USE ) {
+ goto InUse;
+ } else {
+
+ DebugTrace( 0, Dbg, "ConnectScb: Unknown Scb State %08lx\n", pNpScb->State );
+ Status = STATUS_INVALID_PARAMETER;
+ goto CleanupAndExit;
+ }
+
+ } else {
+
+ //
+ // This is a new SCB, we have to run through the whole routine.
+ //
+
+ pNpScb->State = SCB_STATE_ATTACHING;
+ }
+
+ }
+
+GetAddress:
+
+ //
+ // Set the reroute attempted bit so that we don't try
+ // to reconnect during the connect.
+ //
+
+ SetFlag( pIrpContext->Flags, IRP_FLAG_REROUTE_ATTEMPTED );
+
+ if ( !pServerAddress ) {
+
+ //
+ // If we don't have an address, this SCB cannot be anonymous!!
+ //
+
+ ASSERT( !AnonymousScb );
+
+ //
+ // We have to cast an exception frame for this legacy routine
+ // that still uses structured exceptions.
+ //
+
+ try {
+
+ pNpScb = FindServer( pIrpContext, pNpScb, Server );
+
+ ASSERT( pNpScb != NULL );
+
+ //
+ // This is redundant unless the starting server was NULL.
+ // FindServer returns the same SCB we provided to it
+ // unless we called it with NULL.
+ //
+
+ pScb = pNpScb->pScb;
+ pIrpContext->pNpScb = pNpScb;
+ pIrpContext->pScb = pScb;
+ NwAppendToQueueAndWait( pIrpContext );
+
+ } except ( EXCEPTION_EXECUTE_HANDLER ) {
+
+ Status = GetExceptionCode();
+ goto CleanupAndExit;
+ }
+
+ } else {
+
+ //
+ // Build the address into the NpScb since we already know it.
+ //
+
+ RtlCopyMemory( &pNpScb->ServerAddress,
+ pServerAddress,
+ sizeof( TDI_ADDRESS_IPX ) );
+
+ BuildIpxAddress( pNpScb->ServerAddress.Net,
+ pNpScb->ServerAddress.Node,
+ NCP_SOCKET,
+ &pNpScb->RemoteAddress );
+
+ pNpScb->State = SCB_STATE_RECONNECT_REQUIRED;
+
+ }
+
+Connect:
+
+ //
+ // FindServer may have connected us to the server already,
+ // so we may be able to skip the reconnect here.
+ //
+
+ if ( pNpScb->State == SCB_STATE_RECONNECT_REQUIRED ) {
+
+ //
+ // If this is an anonymous scb, we have to be prepared
+ // for ConnectToServer() to find that we've already connected
+ // this server by name. In this case, we cancel the
+ // anonymous create and use the server that was created
+ // while we were looking up the name.
+ //
+
+ Status = ConnectToServer( pIrpContext, &pCollisionScb );
+
+ if (!NT_SUCCESS(Status)) {
+ goto CleanupAndExit;
+ }
+
+ //
+ // We succeeded. If there's a collision scb, then we need to
+ // abandon the anonymous scb and use the scb that we collided
+ // with. Otherwise, we successfully completed an anonymous
+ // connect and can go on with the create normally.
+ //
+
+ if ( pCollisionScb ) {
+
+ ASSERT( AnonymousScb );
+
+ //
+ // Deref and dequeue from the abandoned server.
+ //
+
+ NwDequeueIrpContext( pIrpContext, FALSE );
+ NwDereferenceScb( pIrpContext->pNpScb );
+
+ //
+ // Queue to the appropriate server.
+ //
+
+ pIrpContext->pScb = pCollisionScb;
+ pIrpContext->pNpScb = pCollisionScb->pNpScb;
+ NwAppendToQueueAndWait( pIrpContext );
+
+ pScb = pCollisionScb;
+ pNpScb = pCollisionScb->pNpScb;
+ *Scb = pCollisionScb;
+
+ //
+ // Re-start connecting the scb.
+ //
+
+ AnonymousScb = FALSE;
+ ExistingScb = TRUE;
+
+ pCollisionScb = NULL;
+
+ DebugTrace( 0, Dbg, "Re-doing connect on anonymous collision.\n", 0 );
+ goto RedoConnect;
+
+ }
+
+ DebugTrace( +0, Dbg, " Logout from server - just in case\n", 0);
+
+ Status = ExchangeWithWait (
+ pIrpContext,
+ SynchronousResponseCallback,
+ "F",
+ NCP_LOGOUT );
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto CleanupAndExit;
+ }
+
+ DebugTrace( +0, Dbg, " Connect to real server = %X\n", Status);
+
+ pNpScb->State = SCB_STATE_LOGIN_REQUIRED;
+ }
+
+Login:
+
+ //
+ // If we have credentials for the tree and this server was named
+ // explicitly, we shouldn't defer the login or else the browse
+ // view of the tree may be wrong. For this reason, NdsServerAuthenticate
+ // has to be a straight shot call and can't remove us from the head
+ // of the queue.
+ //
+
+ if ( ( ( Server != NULL ) || ( pServerAddress != NULL ) ) &&
+ ( DeferredLogon ) &&
+ ( pScb->MajorVersion > 3 ) &&
+ ( pScb->UserName.Length == 0 ) ) {
+
+ NwAcquireExclusiveRcb( &NwRcb, TRUE );
+ pLogon = FindUser( &pScb->UserUid, FALSE );
+ NwReleaseRcb( &NwRcb );
+
+ if ( pLogon ) {
+
+ CredStatus = NdsLookupCredentials( &pScb->NdsTreeName,
+ pLogon,
+ &pNdsContext,
+ CREDENTIAL_READ,
+ FALSE );
+
+ if ( NT_SUCCESS( CredStatus ) ) {
+
+ if ( ( pNdsContext->Credential != NULL ) &&
+ ( pNdsContext->CredentialLocked == FALSE ) ) {
+
+ DebugTrace( 0, Dbg, "Forcing authentication to %wZ.\n",
+ &pScb->UidServerName );
+ DeferredLogon = FALSE;
+ }
+
+ NwReleaseCredList( pLogon );
+ }
+ }
+ }
+
+ if (pNpScb->State == SCB_STATE_LOGIN_REQUIRED && !DeferredLogon ) {
+
+ //
+ // NOTE: DoBinderyLogon() and DoNdsLogon() may return a
+ // warning status. If they do, we must return the
+ // warning status to the caller.
+ //
+
+ Status = STATUS_UNSUCCESSFUL;
+ TriedNdsLogin = FALSE;
+
+ //
+ // We force a bindery login for a non 4.x server. Otherwise, we
+ // allow a fall-back from NDS style authentication to bindery style
+ // authentication.
+ //
+
+ if ( pScb->MajorVersion >= 4 ) {
+
+ ASSERT( pScb->NdsTreeName.Length != 0 );
+
+ Status = DoNdsLogon( pIrpContext, UserName, Password );
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ //
+ // Do we need to re-license the connection?
+ //
+
+ if ( ( pScb->VcbCount > 0 ) || ( pScb->OpenNdsStreams > 0 ) ) {
+
+ Status = NdsLicenseConnection( pIrpContext );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ Status = STATUS_REMOTE_SESSION_LIMIT;
+ }
+ }
+
+ }
+
+ TriedNdsLogin = TRUE;
+ LoginStatus = Status;
+
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ Status = DoBinderyLogon( pIrpContext, UserName, Password );
+
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ if ( TriedNdsLogin ) {
+
+ //
+ // Both login attempts have failed. We usually prefer
+ // the NDS status, but not always.
+ //
+
+ if ( ( Status != STATUS_WRONG_PASSWORD ) &&
+ ( Status != STATUS_ACCOUNT_DISABLED ) ) {
+ Status = LoginStatus;
+ }
+ }
+
+ //
+ // Couldn't log on, be good boys and disconnect.
+ //
+
+ ExchangeWithWait (
+ pIrpContext,
+ SynchronousResponseCallback,
+ "D-" ); // Disconnect
+
+ Stats.Sessions--;
+
+ if ( pScb->MajorVersion == 2 ) {
+ Stats.NW2xConnects--;
+ } else if ( pScb->MajorVersion == 3 ) {
+ Stats.NW3xConnects--;
+ } else if ( pScb->MajorVersion == 4 ) {
+ Stats.NW4xConnects--;
+ }
+
+ //
+ // Demote this scb to reconnect required and exit.
+ //
+
+ pNpScb->State = SCB_STATE_RECONNECT_REQUIRED;
+ goto CleanupAndExit;
+ }
+
+ pNpScb->State = SCB_STATE_IN_USE;
+ }
+
+ //
+ // We have to be at the head of the queue to do the reconnect.
+ //
+
+ if ( BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_RECONNECT_ATTEMPT ) ) {
+ ASSERT( pIrpContext->pNpScb->Requests.Flink == &pIrpContext->NextRequest );
+ } else {
+ NwAppendToQueueAndWait( pIrpContext );
+ }
+
+ ReconnectScb( pIrpContext, pScb );
+
+InUse:
+
+ //
+ // Ok, we've completed the connect routine. Return this good server.
+ //
+
+ *Scb = pScb;
+
+CleanupAndExit:
+
+ //
+ // The reconnect path must not do anything to remove the irp context from
+ // the head of the queue since it also owns the irp context in the second
+ // position on the queue and that irp context is running.
+ //
+
+ if ( !BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_RECONNECT_ATTEMPT ) ) {
+ NwDequeueIrpContext( pIrpContext, FALSE );
+ }
+
+ DebugTrace( 0, Dbg, "ConnectScb: Connected %08lx\n", pScb );
+ DebugTrace( 0, Dbg, "ConnectScb: Status was %08lx\n", Status );
+ return Status;
+
+}
+
+NTSTATUS
+CreateScb(
+ OUT PSCB *Scb,
+ IN PIRP_CONTEXT pIrpContext,
+ IN PUNICODE_STRING Server,
+ IN IPXaddress *pServerAddress,
+ IN PUNICODE_STRING UserName,
+ IN PUNICODE_STRING Password,
+ IN BOOLEAN DeferLogon,
+ IN BOOLEAN DeleteConnection
+)
+/*++
+
+Routine Description:
+
+ This routine connects to the requested server.
+
+ The following mix of parameters are valid:
+
+ Server Name, No Net Address - The routine will look up
+ up the SCB or create a new one if necessary, getting
+ the server address from a nearby bindery.
+
+ No Server Name, Valid Net Address - The routine will
+ look up the SCB by address or create a new one if
+ necessary. The name of the server will be set in
+ the SCB upon return.
+
+ Server Name, Valid Net Address - The routine will look
+ up the SCB by name or will create a new one if
+ necessary. The supplied server address will be used,
+ sparing a bindery query.
+
+ No Server Name, No Net Address - A connection to the
+ preferred server or a nearby server will be returned.
+
+Arguments:
+
+ Scb - The pointer to the scb in question.
+ pIrpContext - The information for this request.
+ Server - The name of the server, or NULL.
+ pServerAddress - The address of the server, or NULL.
+ UserName - The username for the connect, or NULL.
+ Password - The password for the connect, or NULL.
+ DeferLogon - Should we defer the logon until later?
+ DeleteConnection - Should we allow this even when there's no
+ net response so that the connection can
+ be deleted?
+
+Return Value:
+
+ NTSTATUS - Status of operation. If the return status is STATUS_SUCCESS,
+ then Scb must point to a valid Scb. The irp context pointers will also
+ be set, but the irp context will not be on the scb queue.
+
+--*/
+{
+
+ NTSTATUS Status = STATUS_UNSUCCESSFUL;
+ PSCB pScb = NULL;
+ PNONPAGED_SCB pOriginalNpScb = pIrpContext->pNpScb;
+ PSCB pOriginalScb = pIrpContext->pScb;
+ BOOLEAN ExistingScb = FALSE;
+ BOOLEAN AnonymousScb = FALSE;
+ PLOGON pLogon;
+ PNDS_SECURITY_CONTEXT pNdsContext;
+
+ PAGED_CODE();
+
+ DebugTrace(+1, Dbg, "CreateScb....\n", 0);
+
+ //
+ // Do not allow any SCB opens unless the redirector is running
+ // unless they are no connect creates and we are waiting to bind.
+ //
+
+ if ( NwRcb.State != RCB_STATE_RUNNING ) {
+
+ if ( ( !BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_NOCONNECT ) ||
+ ( NwRcb.State != RCB_STATE_NEED_BIND ) ) ) {
+
+ *Scb = NULL;
+ DebugTrace( -1, Dbg, "CreateScb -> %08lx\n", STATUS_REDIRECTOR_NOT_STARTED );
+ return STATUS_REDIRECTOR_NOT_STARTED;
+ }
+ }
+
+ if ( UserName != NULL ) {
+ DebugTrace( 0, Dbg, " ->UserName = %wZ\n", UserName );
+ } else {
+ DebugTrace( 0, Dbg, " ->UserName = NULL\n", 0 );
+ }
+
+ if ( Password != NULL ) {
+ DebugTrace( 0, Dbg, " ->Password = %wZ\n", Password );
+ } else {
+ DebugTrace( 0, Dbg, " ->Password = NULL\n", 0 );
+ }
+
+ //
+ // Get the SCB for this server.
+ //
+
+ Status = GetScb( &pScb,
+ pIrpContext,
+ Server,
+ pServerAddress,
+ UserName,
+ Password,
+ DeferLogon,
+ &ExistingScb );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ return Status;
+ }
+
+ //
+ // At this point, we may or may not have an SCB.
+ //
+ // If we have an SCB, we know:
+ //
+ // 1. The scb is referenced.
+ // 2. We are at the head of the queue.
+ //
+ // IMPORTANT POINT: The SCB may be anonymous. If it is,
+ // we do not hold the RCB, but rather we have to re-check
+ // whether or not the server has shown up via a different
+ // create when we find out who the anonymous server is.
+ // We do this because there is a window where we have a
+ // servers name but not its address and so our lookup by
+ // address might be inaccurate.
+ //
+
+ if ( ( pScb ) && IS_ANONYMOUS_SCB( pScb ) ) {
+ AnonymousScb = TRUE;
+ }
+
+ //
+ // If we have a fully connected SCB, we need to go no further.
+ //
+
+ if ( ( pScb ) && ( pScb->pNpScb->State == SCB_STATE_IN_USE ) ) {
+
+ ASSERT( !AnonymousScb );
+
+ if ( ( pScb->MajorVersion >= 4 ) &&
+ ( pScb->UserName.Buffer == NULL ) ) {
+
+ //
+ // This is an NDS authenticated server and we have
+ // to make sure the credentials aren't locked for
+ // logout.
+ //
+
+ NwAcquireExclusiveRcb( &NwRcb, TRUE );
+ pLogon = FindUser( &pScb->UserUid, FALSE );
+ NwReleaseRcb( &NwRcb );
+
+ if ( pLogon ) {
+
+ Status = NdsLookupCredentials( &pScb->NdsTreeName,
+ pLogon,
+ &pNdsContext,
+ CREDENTIAL_READ,
+ FALSE );
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ if ( ( pNdsContext->Credential != NULL ) &&
+ ( pNdsContext->CredentialLocked == TRUE ) ) {
+
+ DebugTrace( 0, Dbg, "Denying create... we're logging out.\n", 0 );
+ Status = STATUS_DEVICE_BUSY;
+ }
+
+ NwReleaseCredList( pLogon );
+ }
+ }
+ }
+
+ NwDequeueIrpContext( pIrpContext, FALSE );
+
+ //
+ // We must not change the irp context pointers if we're going
+ // to fail this call or we may screw up ref counts and what not.
+ //
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ *Scb = pScb;
+
+ } else {
+
+ *Scb = NULL;
+ NwDereferenceScb( pScb->pNpScb );
+
+ pIrpContext->pNpScb = pOriginalNpScb;
+ pIrpContext->pScb = pOriginalScb;
+
+ }
+
+
+ DebugTrace( -1, Dbg, "CreateScb: pScb = %08lx\n", pScb );
+ return Status;
+ }
+
+ //
+ // Run through the connect routines for this scb. The scb may
+ // be NULL if we're still looking for a nearby server.
+ //
+
+ Status = ConnectScb( &pScb,
+ pIrpContext,
+ Server,
+ pServerAddress,
+ UserName,
+ Password,
+ DeferLogon,
+ DeleteConnection,
+ ExistingScb );
+
+ //
+ // If ConnectScb fails, remove the extra ref count so
+ // the scavenger will clean it up. Anonymous failures
+ // are also cleaned up by the scavenger.
+ //
+
+ if ( !NT_SUCCESS( Status ) ) {
+
+ if ( pScb ) {
+ NwDereferenceScb( pScb->pNpScb );
+ }
+
+ //
+ // We must not change the irp context pointers if we're going
+ // to fail this call or we may screw up ref counts and what not.
+ //
+
+ pIrpContext->pNpScb = pOriginalNpScb;
+ pIrpContext->pScb = pOriginalScb;
+ *Scb = NULL;
+
+ DebugTrace( -1, Dbg, "CreateScb: Status = %08lx\n", Status );
+ return Status;
+ }
+
+ //
+ // If ConnectScb succeeds, then we must have an scb, the scb must
+ // be in the IN_USE state (or LOGIN_REQUIRED if DeferLogon was
+ // specified), it must be referenced, and we should not be on the
+ // queue.
+ //
+
+ ASSERT( pScb );
+ ASSERT( !BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_ON_SCB_QUEUE ) );
+ ASSERT( pIrpContext->pNpScb == pScb->pNpScb );
+ ASSERT( pIrpContext->pScb == pScb );
+ ASSERT( pScb->pNpScb->Reference > 0 );
+
+ *Scb = pScb;
+ DebugTrace(-1, Dbg, "CreateScb -> pScb = %08lx\n", pScb );
+ ASSERT( NT_SUCCESS( Status ) );
+
+ return Status;
+}
+
+PNONPAGED_SCB
+FindServer(
+ PIRP_CONTEXT pIrpContext,
+ PNONPAGED_SCB pNpScb,
+ PUNICODE_STRING ServerName
+ )
+/*++
+
+Routine Description:
+
+ This routine attempts to get the network address of a server. If no
+ servers are known, it first sends a find nearest SAP.
+
+Arguments:
+
+ pIrpContext - A pointer to the request parameters.
+
+ pNpScb - A pointer to the non paged SCB for the server to get the
+ address of.
+
+Return Value:
+
+ NONPAGED_SCB - A pointer the nonpaged SCB. This is the same as the
+ input value, unless the input SCB was NULL. Then this is a
+ pointer to the nearest server SCB.
+
+ This routine raises status if it fails to get the server's address.
+
+--*/
+{
+ NTSTATUS Status;
+ ULONG Attempts;
+ BOOLEAN FoundServer = FALSE;
+ PNONPAGED_SCB pNearestNpScb = NULL;
+ PNONPAGED_SCB pLastNpScb = NULL;
+
+ BOOLEAN SentFindNearest = FALSE;
+ BOOLEAN SentGeneral = FALSE;
+ PMDL ReceiveMdl = NULL;
+ PUCHAR ReceiveBuffer = NULL;
+ IPXaddress ServerAddress;
+
+ BOOLEAN ConnectedToNearest = FALSE;
+ BOOLEAN AllocatedIrpContext = FALSE;
+ PIRP_CONTEXT pNewIrpContext;
+ int ResponseCount;
+ int NewServers;
+
+ static LARGE_INTEGER TimeoutWait = {0,0};
+ LARGE_INTEGER Now;
+
+ PAGED_CODE();
+
+ //
+ // If we had a SAP timeout less than 10 seconds ago, just fail this
+ // request immediately. This allows dumb apps to exit a lot faster.
+ //
+
+ KeQuerySystemTime( &Now );
+ if ( Now.QuadPart < TimeoutWait.QuadPart ) {
+ ExRaiseStatus( STATUS_BAD_NETWORK_PATH );
+ }
+
+ try {
+ for ( Attempts = 0; Attempts < MAX_SAP_RETRIES && !FoundServer ; Attempts++ ) {
+
+ //
+ // If this SCB is now marked RECONNECT_REQUIRED, then
+ // it responded to the find nearest and we can immediately
+ // try to connect to it.
+ //
+
+ if ( pNpScb != NULL &&
+ pNpScb->State == SCB_STATE_RECONNECT_REQUIRED ) {
+
+ return pNpScb;
+ }
+
+ //
+ // Pick a server to use to find the address of the server that
+ // we are really interested in.
+ //
+
+ if (pLastNpScb) {
+
+ //
+ // For some reason we couldn't use pNearestScb. Scan from this
+ // server onwards.
+ //
+
+ pNearestNpScb = SelectConnection( pLastNpScb );
+
+ // Allow pLastNpScb to be deleted.
+
+ NwDereferenceScb( pLastNpScb );
+
+ pLastNpScb = NULL;
+
+ } else {
+
+ pNearestNpScb = SelectConnection( NULL );
+
+ }
+
+ if ( pNearestNpScb == NULL ) {
+
+ int i;
+
+ //
+ // If we sent a find nearest, and still don't have a single
+ // entry in the server list, it's time to give up.
+ //
+
+ if (( SentFindNearest) &&
+ ( SentGeneral )) {
+
+ Error(
+ EVENT_NWRDR_NO_SERVER_ON_NETWORK,
+ STATUS_OBJECT_NAME_NOT_FOUND,
+ NULL,
+ 0,
+ 0 );
+
+ ExRaiseStatus( STATUS_BAD_NETWORK_PATH );
+ return NULL;
+ }
+
+ //
+ // We don't have any active servers in the list. Queue our
+ // IrpContext to the NwPermanentNpScb. This insures that
+ // only one thread in the system in doing a find nearest at
+ // any one time.
+ //
+
+ DebugTrace( +0, Dbg, " Nearest Server\n", 0);
+
+ if ( !AllocatedIrpContext ) {
+ AllocatedIrpContext = NwAllocateExtraIrpContext(
+ &pNewIrpContext,
+ &NwPermanentNpScb );
+
+ if ( !AllocatedIrpContext ) {
+ ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
+ }
+ }
+
+ pNewIrpContext->pNpScb = &NwPermanentNpScb;
+
+ //
+ // Allocate an extra buffer large enough for 4
+ // find nearest responses, or a general SAP response.
+ //
+
+ pNewIrpContext->Specific.Create.FindNearestResponseCount = 0;
+ NewServers = 0;
+
+
+ ReceiveBuffer = ALLOCATE_POOL_EX(
+ NonPagedPool,
+ MAX_SAP_RESPONSE_SIZE );
+
+ pNewIrpContext->Specific.Create.FindNearestResponse[0] = ReceiveBuffer;
+
+ for ( i = 1; i < MAX_SAP_RESPONSES ; i++ ) {
+ pNewIrpContext->Specific.Create.FindNearestResponse[i] =
+ ReceiveBuffer + i * SAP_RECORD_SIZE;
+ }
+
+ //
+ // Get the tick count for this net, so that we know how
+ // long to wait for SAP responses.
+ //
+
+ (VOID)GetTickCount( pNewIrpContext, &NwPermanentNpScb.TickCount );
+ NwPermanentNpScb.SendTimeout = NwPermanentNpScb.TickCount + 10;
+
+ if (!SentFindNearest) {
+
+ //
+ // Send a find nearest SAP, and wait for up to several
+ // responses. This allows us to handle a busy server
+ // that responds quickly to SAPs but will not accept
+ // connections.
+ //
+
+ Status = ExchangeWithWait (
+ pNewIrpContext,
+ ProcessFindNearest,
+ "Aww",
+ SAP_FIND_NEAREST,
+ SAP_SERVICE_TYPE_SERVER );
+
+ if ( Status == STATUS_NETWORK_UNREACHABLE ) {
+
+ //
+ // IPX is not bound to anything that is currently
+ // up (which means it's probably bound only to the
+ // RAS WAN wrapper). Don't waste 20 seconds trying
+ // to find a server.
+ //
+
+ DebugTrace( 0, Dbg, "Aborting FindNearest. No Net.\n", 0 );
+ NwDequeueIrpContext( pNewIrpContext, FALSE );
+ ExRaiseStatus( STATUS_NETWORK_UNREACHABLE );
+ }
+
+ //
+ // Process the set of find nearest responses.
+ //
+
+ for (i = 0; i < (int)pNewIrpContext->Specific.Create.FindNearestResponseCount; i++ ) {
+ if (ProcessFindNearestEntry(
+ pNewIrpContext,
+ (PSAP_FIND_NEAREST_RESPONSE)pNewIrpContext->Specific.Create.FindNearestResponse[i] )
+ ) {
+
+ //
+ // We found a server that was previously unknown.
+ //
+
+ NewServers++;
+ }
+ }
+ }
+
+ if (( !NewServers ) &&
+ ( !SentGeneral)){
+
+ SentGeneral = TRUE;
+
+ //
+ // Either no SAP responses or can't connect to nearest servers.
+ // Try a general SAP.
+ //
+
+ ReceiveMdl = ALLOCATE_MDL(
+ ReceiveBuffer,
+ MAX_SAP_RESPONSE_SIZE,
+ TRUE,
+ FALSE,
+ NULL );
+
+ if ( ReceiveMdl == NULL ) {
+ ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
+ }
+
+ MmBuildMdlForNonPagedPool( ReceiveMdl );
+ pNewIrpContext->RxMdl->Next = ReceiveMdl;
+
+ Status = ExchangeWithWait (
+ pNewIrpContext,
+ SynchronousResponseCallback,
+ "Aww",
+ SAP_GENERAL_REQUEST,
+ SAP_SERVICE_TYPE_SERVER );
+
+ if ( NT_SUCCESS( Status ) ) {
+ DebugTrace( 0, Dbg, "Received %d bytes\n", pNewIrpContext->ResponseLength );
+ ResponseCount = ( pNewIrpContext->ResponseLength - 2 ) / SAP_RECORD_SIZE;
+
+ //
+ // Process at most MAX_SAP_RESPONSES servers.
+ //
+
+ if ( ResponseCount > MAX_SAP_RESPONSES ) {
+ ResponseCount = MAX_SAP_RESPONSES;
+ }
+
+ for ( i = 0; i < ResponseCount; i++ ) {
+ ProcessFindNearestEntry(
+ pNewIrpContext,
+ (PSAP_FIND_NEAREST_RESPONSE)(pNewIrpContext->rsp + SAP_RECORD_SIZE * i) );
+ }
+ }
+
+ pNewIrpContext->RxMdl->Next = NULL;
+ FREE_MDL( ReceiveMdl );
+ ReceiveMdl = NULL;
+ }
+
+ //
+ // We're done with the find nearest. Free the buffer and
+ // dequeue from the permanent SCB.
+ //
+
+ FREE_POOL( ReceiveBuffer );
+ ReceiveBuffer = NULL;
+ NwDequeueIrpContext( pNewIrpContext, FALSE );
+
+ if ( !NT_SUCCESS( Status ) &&
+ pNewIrpContext->Specific.Create.FindNearestResponseCount == 0 ) {
+
+ //
+ // If the SAP timed out, map the error for MPR.
+ //
+
+ if ( Status == STATUS_REMOTE_NOT_LISTENING ) {
+ Status = STATUS_BAD_NETWORK_PATH;
+ }
+
+ //
+ // Setup the WaitTimeout, and fail this request.
+ //
+
+ KeQuerySystemTime( &TimeoutWait );
+ TimeoutWait.QuadPart += NwOneSecond * 10;
+
+ ExRaiseStatus( Status );
+ return NULL;
+ }
+
+ SentFindNearest = TRUE;
+
+ } else {
+
+ if ( !AllocatedIrpContext ) {
+ AllocatedIrpContext = NwAllocateExtraIrpContext(
+ &pNewIrpContext,
+ pNearestNpScb );
+
+ if ( !AllocatedIrpContext ) {
+ ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
+ }
+ }
+
+ //
+ // Point the IRP context at the nearest server.
+ //
+
+ pNewIrpContext->pNpScb = pNearestNpScb;
+ NwAppendToQueueAndWait( pNewIrpContext );
+
+ if ( pNearestNpScb->State == SCB_STATE_RECONNECT_REQUIRED ) {
+
+ //
+ // We have no connection to this server, try to
+ // connect now. This is not a valid path for an
+ // anonymous create, so there's no chance that
+ // there will be a name collision.
+ //
+
+ Status = ConnectToServer( pNewIrpContext, NULL );
+ if ( !NT_SUCCESS( Status ) ) {
+
+ //
+ // Failed to connect to the server. Give up.
+ // We'll try another server.
+ //
+
+ NwDequeueIrpContext( pNewIrpContext, FALSE );
+
+ // Keep pNearestScb referenced
+ // so it doesn't disappear.
+
+ pLastNpScb = pNearestNpScb;
+
+ continue;
+
+ } else {
+
+ pNearestNpScb->State = SCB_STATE_LOGIN_REQUIRED;
+ ConnectedToNearest = TRUE;
+
+ }
+ }
+
+ //
+ // update the last used time for this SCB.
+ //
+
+ KeQuerySystemTime( &pNearestNpScb->LastUsedTime );
+
+ if (( pNpScb == NULL ) ||
+ ( ServerName == NULL )) {
+
+ //
+ // We're looking for any server so use this one.
+ //
+ // We'll exit the for loop on the SCB queue,
+ // and with this SCB referenced.
+ //
+
+ pNpScb = pNearestNpScb;
+ Status = STATUS_SUCCESS;
+ FoundServer = TRUE;
+ NwDequeueIrpContext( pNewIrpContext, FALSE );
+
+ } else {
+
+ Status = QueryServersAddress(
+ pNewIrpContext,
+ pNearestNpScb,
+ ServerName,
+ &ServerAddress );
+
+ //
+ // If we connect to this server just to query it's
+ // bindery, disconnect now.
+ //
+
+ if ( ConnectedToNearest && NT_SUCCESS(Status) ) {
+ ExchangeWithWait (
+ pNewIrpContext,
+ SynchronousResponseCallback,
+ "D-" ); // Disconnect
+
+ pNearestNpScb->State = SCB_STATE_RECONNECT_REQUIRED;
+ }
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ //
+ // Success!
+ //
+ // Point the SCB at the real server address and connect to it,
+ // then logout. (We logout for no apparent reason except
+ // because this is what a netware redir does.)
+ //
+
+ RtlCopyMemory(
+ &pNpScb->ServerAddress,
+ &ServerAddress,
+ sizeof( TDI_ADDRESS_IPX ) );
+
+ BuildIpxAddress(
+ ServerAddress.Net,
+ ServerAddress.Node,
+ NCP_SOCKET,
+ &pNpScb->RemoteAddress );
+
+ FoundServer = TRUE;
+
+ NwDequeueIrpContext( pNewIrpContext, FALSE );
+ NwDereferenceScb( pNearestNpScb );
+
+ pNewIrpContext->pNpScb = pNpScb;
+ pNpScb->State = SCB_STATE_RECONNECT_REQUIRED;
+
+ } else {
+
+ NwDequeueIrpContext( pNewIrpContext, FALSE );
+
+ if ( Status == STATUS_REMOTE_NOT_LISTENING ) {
+
+ //
+ // This server is no longer talking to us.
+ // Try again. Keep pNearestScb referenced
+ // so it doesn't disappear.
+ //
+
+ pLastNpScb = pNearestNpScb;
+
+ continue;
+
+ } else {
+
+ NwDereferenceScb( pNearestNpScb );
+
+ //
+ // This nearest server doesn't know about
+ // the server we are looking for. Give up
+ // and let another rdr try.
+ //
+
+ ExRaiseStatus( STATUS_BAD_NETWORK_PATH );
+ return NULL;
+ }
+ }
+ }
+
+ } // else
+ } // for
+
+ } finally {
+
+ if ( ReceiveBuffer != NULL ) {
+ FREE_POOL( ReceiveBuffer );
+ }
+
+ if ( ReceiveMdl != NULL ) {
+ FREE_MDL( ReceiveMdl );
+ }
+
+ if ( AllocatedIrpContext ) {
+ NwFreeExtraIrpContext( pNewIrpContext );
+ }
+
+ if (pLastNpScb) {
+ NwDereferenceScb( pLastNpScb );
+ }
+
+ }
+
+ if ( !FoundServer ) {
+ ExRaiseStatus( STATUS_BAD_NETWORK_PATH );
+ }
+
+ return pNpScb;
+}
+
+
+NTSTATUS
+ProcessFindNearest(
+ IN struct _IRP_CONTEXT* pIrpContext,
+ IN ULONG BytesAvailable,
+ IN PUCHAR Response
+ )
+/*++
+
+Routine Description:
+
+ This routine takes the full address of the remote server and builds
+ the corresponding TA_IPX_ADDRESS.
+
+Arguments:
+
+
+Return Value:
+
+
+--*/
+
+{
+ ULONG ResponseCount;
+ KIRQL OldIrql;
+
+ DebugTrace(+1, Dbg, "ProcessFindNearest...\n", 0);
+
+ KeAcquireSpinLock( &ScbSpinLock, &OldIrql );
+
+ if ( BytesAvailable == 0) {
+
+ //
+ // Timeout.
+ //
+
+ pIrpContext->ResponseParameters.Error = 0;
+ pIrpContext->pNpScb->OkToReceive = FALSE;
+
+ ASSERT( pIrpContext->Event.Header.SignalState == 0 );
+#if NWDBG
+ pIrpContext->DebugValue = 0x101;
+#endif
+ NwSetIrpContextEvent( pIrpContext );
+ DebugTrace(-1, Dbg, "ProcessFindNearest -> %08lx\n", STATUS_REMOTE_NOT_LISTENING);
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql );
+ return STATUS_REMOTE_NOT_LISTENING;
+ }
+
+ if ( BytesAvailable >= FIND_NEAREST_RESP_SIZE &&
+ Response[0] == 0 &&
+ Response[1] == SAP_SERVICE_TYPE_SERVER ) {
+
+ //
+ // This is a valid find nearest response. Process the packet.
+ //
+
+ ResponseCount = pIrpContext->Specific.Create.FindNearestResponseCount++;
+ ASSERT( ResponseCount < MAX_SAP_RESPONSES );
+
+ //
+ // Copy the Find Nearest server response to the receive buffer.
+ //
+
+ RtlCopyMemory(
+ pIrpContext->Specific.Create.FindNearestResponse[ResponseCount],
+ Response,
+ FIND_NEAREST_RESP_SIZE );
+
+ //
+ // If we have reached critical mass on the number of find
+ // nearest responses, set the event to indicate that we
+ // are done.
+ //
+
+ if ( ResponseCount == MAX_SAP_RESPONSES - 1 ) {
+
+ ASSERT( pIrpContext->Event.Header.SignalState == 0 );
+#ifdef NWDBG
+ pIrpContext->DebugValue = 0x102;
+#endif
+ pIrpContext->ResponseParameters.Error = 0;
+ NwSetIrpContextEvent( pIrpContext );
+
+ } else {
+ pIrpContext->pNpScb->OkToReceive = TRUE;
+ }
+
+ } else {
+
+ //
+ // Discard the invalid find nearest response.
+ //
+
+ pIrpContext->pNpScb->OkToReceive = TRUE;
+ }
+
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql );
+
+ DebugTrace(-1, Dbg, "ProcessFindNearest -> %08lx\n", STATUS_SUCCESS );
+ return( STATUS_SUCCESS );
+}
+
+BOOLEAN
+ProcessFindNearestEntry(
+ PIRP_CONTEXT IrpContext,
+ PSAP_FIND_NEAREST_RESPONSE FindNearestResponse
+ )
+{
+ OEM_STRING OemServerName;
+ UNICODE_STRING UidServerName;
+ UNICODE_STRING ServerName;
+ NTSTATUS Status;
+ PSCB pScb;
+ PNONPAGED_SCB pNpScb = NULL;
+ BOOLEAN ExistingScb = TRUE;
+
+ PAGED_CODE();
+
+ DebugTrace(+1, Dbg, "ProcessFindNearestEntry\n", 0);
+
+ ServerName.Buffer = NULL;
+ UidServerName.Buffer = NULL;
+
+ try {
+
+ RtlInitString( &OemServerName, FindNearestResponse->ServerName );
+ ASSERT( OemServerName.Length < MAX_SERVER_NAME_LENGTH * sizeof( WCHAR ) );
+
+ Status = RtlOemStringToCountedUnicodeString(
+ &ServerName,
+ &OemServerName,
+ TRUE );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ try_return( NOTHING );
+ }
+
+ //
+ // Lookup of the SCB by name. If it is not found, an SCB
+ // will be created.
+ //
+
+ Status = MakeUidServer(
+ &UidServerName,
+ &IrpContext->Specific.Create.UserUid,
+ &ServerName );
+
+ if (!NT_SUCCESS(Status)) {
+ try_return( NOTHING );
+ }
+
+ ExistingScb = NwFindScb( &pScb, IrpContext, &UidServerName, &ServerName );
+ ASSERT( pScb != NULL );
+ pNpScb = pScb->pNpScb;
+
+ //
+ // Copy the network address to the SCB, and calculate the
+ // IPX address.
+ //
+
+ RtlCopyMemory(
+ &pNpScb->ServerAddress,
+ &FindNearestResponse->Network,
+ sizeof( TDI_ADDRESS_IPX ) );
+
+ BuildIpxAddress(
+ pNpScb->ServerAddress.Net,
+ pNpScb->ServerAddress.Node,
+ NCP_SOCKET,
+ &pNpScb->RemoteAddress );
+
+ if ( pNpScb->State == SCB_STATE_ATTACHING ) {
+
+ //
+ // We are in the process of trying to connect to this
+ // server so mark it reconnect required so that
+ // CreateScb will know that we've found it address.
+ //
+
+ pNpScb->State = SCB_STATE_RECONNECT_REQUIRED;
+ }
+
+try_exit: NOTHING;
+
+ } finally {
+
+ if ( pNpScb != NULL ) {
+ NwDereferenceScb( pNpScb );
+ }
+
+ if (UidServerName.Buffer != NULL) {
+ FREE_POOL(UidServerName.Buffer);
+ }
+
+ RtlFreeUnicodeString( &ServerName );
+ }
+
+ //
+ // Tell the caller if we created a new Scb
+ //
+
+
+ if (ExistingScb) {
+ DebugTrace(-1, Dbg, "ProcessFindNearestEntry ->%08lx\n", FALSE );
+ return FALSE;
+ } else {
+ DebugTrace(-1, Dbg, "ProcessFindNearestEntry ->%08lx\n", TRUE );
+ return TRUE;
+ }
+}
+
+
+NTSTATUS
+ConnectToServer(
+ IN struct _IRP_CONTEXT* pIrpContext,
+ OUT PSCB *pScbCollision
+ )
+/*++
+
+Routine Description:
+
+ This routine transfers connect and negotiate buffer NCPs to the server.
+
+ This routine may be called upon to connect an anonymous scb. Upon
+ learning the name of the anonymous scb, it will determine if another
+ create has completed while the name lookup was in progress. If it has,
+ then the routine will refer the called to that new scb. Otherwise, the
+ scb will be entered onto the scb list and used normally. The RCB
+ protects the scb list by name only. For more info, see the comment
+ in CreateScb().
+
+Arguments:
+
+ pIrpContext - supplies context and server information
+
+Return Value:
+
+ Status of operation
+
+--*/
+{
+ NTSTATUS Status, BurstStatus;
+ PNONPAGED_SCB pNpScb = pIrpContext->pNpScb;
+ PSCB pScb = pNpScb->pScb;
+ BOOLEAN AnonymousScb = IS_ANONYMOUS_SCB( pScb );
+ ULONG MaxSafeSize ;
+ BOOLEAN LIPNegotiated ;
+ PLOGON Logon;
+
+ OEM_STRING OemServerName;
+ UNICODE_STRING ServerName;
+ UNICODE_STRING CredentialName;
+ PUNICODE_STRING puConnectName;
+ BYTE OemName[MAX_SERVER_NAME_LENGTH];
+ WCHAR Server[MAX_SERVER_NAME_LENGTH];
+ KIRQL OldIrql;
+ UNICODE_STRING UidServerName;
+ BOOLEAN Success;
+ PLIST_ENTRY ScbQueueEntry;
+ PUNICODE_PREFIX_TABLE_ENTRY PrefixEntry;
+
+ PAGED_CODE();
+
+ DebugTrace( +0, Dbg, " Connect\n", 0);
+
+ //
+ // Get the tick count for our connection to this server
+ //
+
+ Status = GetTickCount( pIrpContext, &pNpScb->TickCount );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ pNpScb->TickCount = DEFAULT_TICK_COUNT;
+ }
+
+ pNpScb->SendTimeout = pNpScb->TickCount + 10;
+
+ //
+ // Initialize timers for a server that supports burst but not LIP
+ //
+
+ pNpScb->NwLoopTime = pNpScb->NwSingleBurstPacketTime = pNpScb->SendTimeout;
+ pNpScb->NwReceiveDelay = pNpScb->NwSendDelay = 0;
+
+ pNpScb->NtSendDelay.QuadPart = 0;
+
+ //
+ // Request connection
+ //
+
+ Status = ExchangeWithWait (
+ pIrpContext,
+ SynchronousResponseCallback,
+ "C-");
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if (!NT_SUCCESS(Status)) {
+ if ( Status == STATUS_UNSUCCESSFUL ) {
+#ifdef QFE_BUILD
+ Status = STATUS_TOO_MANY_SESSIONS;
+#else
+ Status = STATUS_REMOTE_SESSION_LIMIT;
+#endif
+ pNpScb->State = SCB_STATE_ATTACHING;
+
+ } else if ( Status == STATUS_REMOTE_NOT_LISTENING ) {
+
+ //
+ // The connect timed out, suspect that the server is down
+ // and put it back in the attaching state.
+ //
+
+ pNpScb->State = SCB_STATE_ATTACHING;
+ }
+
+ return( Status );
+ }
+
+ pNpScb->SequenceNo++;
+
+ Stats.Sessions++;
+
+ //
+ // Get server information
+ //
+
+ DebugTrace( +0, Dbg, "Get file server information\n", 0);
+
+ Status = ExchangeWithWait ( pIrpContext,
+ SynchronousResponseCallback,
+ "S",
+ NCP_ADMIN_FUNCTION, NCP_GET_SERVER_INFO );
+
+ if ( NT_SUCCESS( Status ) ) {
+ Status = ParseResponse( pIrpContext,
+ pIrpContext->rsp,
+ pIrpContext->ResponseLength,
+ "Nrbb",
+ OemName,
+ MAX_SERVER_NAME_LENGTH,
+ &pScb->MajorVersion,
+ &pScb->MinorVersion );
+ }
+
+ if (!NT_SUCCESS(Status)) {
+ return(Status);
+ }
+
+ //
+ // If this was an anonymous SCB, we need to check the name
+ // for a create collision before we do anything else.
+ //
+
+ if ( AnonymousScb ) {
+
+ //
+ // Grab the RCB to protect the server prefix table. We've
+ // spent the time sending the packet to look up the server
+ // name so we are a little greedy with the RCB to help
+ // minimize the chance of a collision.
+ //
+
+ NwAcquireExclusiveRcb( &NwRcb, TRUE );
+
+ //
+ // Make the uid server name.
+ //
+
+ OemServerName.Buffer = OemName;
+ OemServerName.Length = 0;
+ OemServerName.MaximumLength = sizeof( OemName );
+
+ while ( ( OemServerName.Length < MAX_SERVER_NAME_LENGTH ) &&
+ ( OemName[OemServerName.Length] != '\0' ) ) {
+ OemServerName.Length++;
+ }
+
+ ServerName.Buffer = Server;
+ ServerName.MaximumLength = sizeof( Server );
+ ServerName.Length = 0;
+
+ RtlOemStringToUnicodeString( &ServerName,
+ &OemServerName,
+ FALSE );
+
+ //
+ // If this is an extended credential create, munge the server name.
+ //
+
+ RtlInitUnicodeString( &CredentialName, NULL );
+
+ if ( pIrpContext->Specific.Create.fExCredentialCreate ) {
+
+ Status = BuildExCredentialServerName( &ServerName,
+ pIrpContext->Specific.Create.puCredentialName,
+ &CredentialName );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NwReleaseRcb( &NwRcb );
+ return Status;
+ }
+
+ puConnectName = &CredentialName;
+
+ } else {
+
+ puConnectName = &ServerName;
+ }
+
+ //
+ // Tack on the uid.
+ //
+
+ Status = MakeUidServer( &UidServerName,
+ &pScb->UserUid,
+ puConnectName );
+
+ if ( CredentialName.Buffer ) {
+ FREE_POOL( CredentialName.Buffer );
+ }
+
+ if ( !NT_SUCCESS( Status ) ) {
+ NwReleaseRcb( &NwRcb );
+ return Status;
+ }
+
+ //
+ // Actually do the look up in the prefix table.
+ //
+
+ PrefixEntry = RtlFindUnicodePrefix( &NwRcb.ServerNameTable, &UidServerName, 0 );
+
+ if ( PrefixEntry != NULL ) {
+
+ //
+ // There was a collision with this anonymous create. Dump
+ // the anonymous scb and pick up the new one.
+ //
+
+ NwReleaseRcb( &NwRcb );
+ DebugTrace( 0, DEBUG_TRACE_ALWAYS, "Anonymous create collided for %wZ.\n", &UidServerName );
+
+ //
+ // Disconnect this connection so we don't clutter the server.
+ //
+
+ ExchangeWithWait ( pIrpContext,
+ SynchronousResponseCallback,
+ "D-" );
+
+ FREE_POOL( UidServerName.Buffer );
+
+ //
+ // Since there was a collision, we know for a fact that there's another
+ // good SCB for this server somewhere. We set the state on this anonymous
+ // SCB to SCB_STATE_FLAG_SHUTDOWN so that no one ever plays with the
+ // anonymous SCB again. The scavenger will clean it up soon.
+ //
+
+ pNpScb->State = SCB_STATE_FLAG_SHUTDOWN;
+
+ if ( pScbCollision ) {
+ *pScbCollision = CONTAINING_RECORD( PrefixEntry, SCB, PrefixEntry );
+ NwReferenceScb( (*pScbCollision)->pNpScb );
+ return STATUS_SUCCESS;
+ } else {
+ DebugTrace( 0, Dbg, "Invalid path for an anonymous create.\n", 0 );
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ }
+
+ //
+ // This anonymous create didn't collide - cool! Fill in the server
+ // name, check the preferred, server setting, and put the SCB on the
+ // SCB queue in the correct location. This code is similar to pieces
+ // of code in NwAllocateAndInitScb() and NwFindScb().
+ //
+
+ DebugTrace( 0, Dbg, "Completing anonymous create for %wZ!\n", &UidServerName );
+
+ RtlCopyUnicodeString ( &pScb->UidServerName, &UidServerName );
+ pScb->UidServerName.Buffer[ UidServerName.Length / sizeof( WCHAR ) ] = L'\0';
+
+ pScb->UnicodeUid = pScb->UidServerName;
+ pScb->UnicodeUid.Length = UidServerName.Length -
+ puConnectName->Length -
+ sizeof(WCHAR);
+
+ //
+ // Make ServerName point partway down the buffer for UidServerName
+ //
+
+ pNpScb->ServerName.Buffer = (PWSTR)((PUCHAR)pScb->UidServerName.Buffer +
+ UidServerName.Length - puConnectName->Length);
+
+ pNpScb->ServerName.MaximumLength = puConnectName->Length;
+ pNpScb->ServerName.Length = puConnectName->Length;
+
+ //
+ // Determine if this is our preferred server.
+ //
+
+ Logon = FindUser( &pScb->UserUid, FALSE );
+
+ if (( Logon != NULL) &&
+ (RtlCompareUnicodeString( puConnectName, &Logon->ServerName, TRUE ) == 0 )) {
+ pScb->PreferredServer = TRUE;
+ NwReferenceScb( pNpScb );
+ }
+
+ FREE_POOL( UidServerName.Buffer );
+
+ //
+ // Insert the name of this server into the prefix table.
+ //
+
+ Success = RtlInsertUnicodePrefix( &NwRcb.ServerNameTable,
+ &pScb->UidServerName,
+ &pScb->PrefixEntry );
+
+#ifdef NWDBG
+ if ( !Success ) {
+ DebugTrace( 0, DEBUG_TRACE_ALWAYS, "Entering duplicate SCB %wZ.\n", &pScb->UidServerName );
+ DbgBreakPoint();
+ }
+#endif
+
+ //
+ // This create is complete, release the RCB.
+ //
+
+ NwReleaseRcb( &NwRcb );
+
+ //
+ // If this is our preferred server, we have to move this guy
+ // to the head of the scb list. We do this after the create
+ // because we can't acquire the ScbSpinLock while holding the
+ // RCB.
+ //
+
+ if ( pScb->PreferredServer ) {
+
+ KeAcquireSpinLock(&ScbSpinLock, &OldIrql);
+ RemoveEntryList( &pNpScb->ScbLinks );
+ InsertHeadList( &ScbQueue, &pNpScb->ScbLinks );
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql );
+ }
+
+ }
+
+ if ( pScb->MajorVersion == 2 ) {
+
+ Stats.NW2xConnects++;
+ pNpScb->PageAlign = TRUE;
+
+ } else if ( pScb->MajorVersion == 3 ) {
+
+ Stats.NW3xConnects++;
+
+ if (pScb->MinorVersion > 0xb) {
+ pNpScb->PageAlign = FALSE;
+ } else {
+ pNpScb->PageAlign = TRUE;
+ }
+
+ } else if ( pScb->MajorVersion == 4 ) {
+
+ Stats.NW4xConnects++;
+ pNpScb->PageAlign = FALSE;
+
+ NdsPing( pIrpContext, pScb );
+
+ }
+
+ //
+ // Get the local net max packet size. This is the max frame size
+ // does not include space for IPX or lower level headers.
+ //
+
+ Status = GetMaximumPacketSize( pIrpContext, &pNpScb->Server, &pNpScb->MaxPacketSize );
+
+ //
+ // If the transport won't tell us, pick the largest size that
+ // is guaranteed to work.
+ //
+ if ( !NT_SUCCESS( Status ) ) {
+ pNpScb->BufferSize = DEFAULT_PACKET_SIZE;
+ pNpScb->MaxPacketSize = DEFAULT_PACKET_SIZE;
+ } else {
+ pNpScb->BufferSize = (USHORT)pNpScb->MaxPacketSize;
+ }
+ MaxSafeSize = pNpScb->MaxPacketSize ;
+
+ //
+ // Negotiate a burst mode connection. Keep track of that status.
+ //
+
+ Status = NegotiateBurstMode( pIrpContext, pNpScb, &LIPNegotiated );
+ BurstStatus = Status ;
+
+ if (!NT_SUCCESS(Status) || !LIPNegotiated) {
+
+ //
+ // Negotiate buffer size with server if we didnt do burst
+ // sucessfully or if burst succeeded but we didnt do LIP.
+ //
+
+ DebugTrace( +0, Dbg, "Negotiate Buffer Size\n", 0);
+
+ Status = ExchangeWithWait ( pIrpContext,
+ SynchronousResponseCallback,
+ "Fw",
+ NCP_NEGOTIATE_BUFFER_SIZE,
+ pNpScb->BufferSize );
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+ DebugTrace( +0, Dbg, " Parse response\n", 0);
+
+ if ( NT_SUCCESS( Status ) ) {
+ Status = ParseResponse( pIrpContext,
+ pIrpContext->rsp,
+ pIrpContext->ResponseLength,
+ "Nw",
+ &pNpScb->BufferSize );
+
+ //
+ // Dont allow the server to fool us into using a
+ // packet size bigger than what the media can support.
+ // We have at least one case of server returning 4K while
+ // on ethernet.
+ //
+ // Use PacketThreshold so that the PacketAdjustment can be
+ // avoided on small packet sizes such as those on ethernet.
+ //
+
+ if (MaxSafeSize > (ULONG)PacketThreshold) {
+ MaxSafeSize -= (ULONG)LargePacketAdjustment;
+ }
+
+ //
+ // If larger than number we got from transport, taking in account
+ // IPX header (30) & NCP header (BURST_RESPONSE is a good worst
+ // case), we adjust accordingly.
+ //
+ if (pNpScb->BufferSize >
+ (MaxSafeSize - (30 + sizeof(NCP_BURST_READ_RESPONSE))))
+ {
+ pNpScb->BufferSize = (USHORT)
+ (MaxSafeSize - (30 + sizeof(NCP_BURST_READ_RESPONSE))) ;
+ }
+
+ //
+ // An SFT III server responded with a BufferSize of 0!
+ //
+
+ pNpScb->BufferSize = MAX(pNpScb->BufferSize,DEFAULT_PACKET_SIZE);
+
+ //
+ // If an explicit registry default was set, we honour that.
+ // Note that this only applies in the 'default' case, ie. we
+ // didnt negotiate LIP successfully. Typically, we dont
+ // expect to use this, because the server will drop to 512 if
+ // it finds routers in between. But if for some reason the server
+ // came back with a number that was higher than what some router
+ // in between can take, we have this as manual override.
+ //
+
+ if (DefaultMaxPacketSize != 0)
+ {
+ pNpScb->BufferSize = MIN (pNpScb->BufferSize,
+ (USHORT)DefaultMaxPacketSize) ;
+ }
+ }
+
+ if (NT_SUCCESS(BurstStatus)) {
+ //
+ // We negotiated burst but not LIP. Save the packet size we
+ // have from above and renegotiate the burst so that the
+ // server knows how much it can send to us. And then take
+ // the minimum of the two to make sure we are safe.
+ //
+ USHORT SavedPacketSize = pNpScb->BufferSize ;
+
+ Status = NegotiateBurstMode( pIrpContext, pNpScb, &LIPNegotiated );
+
+ pNpScb->BufferSize = MIN(pNpScb->BufferSize,SavedPacketSize) ;
+ }
+ }
+
+ return Status;
+}
+
+
+NTSTATUS
+NegotiateBurstMode(
+ PIRP_CONTEXT pIrpContext,
+ PNONPAGED_SCB pNpScb,
+ BOOLEAN *LIPNegotiated
+ )
+/*++
+
+Routine Description:
+
+ This routine negotiates a burst mode connection with the specified
+ server.
+
+Arguments:
+
+ pIrpContext - Supplies context and server information.
+
+ pNpScb - A pointer to the NONPAGED_SCB for the server we are
+ negotiating with.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PAGED_CODE();
+
+ *LIPNegotiated = FALSE ;
+
+ if (pNpScb->MaxPacketSize == DEFAULT_PACKET_SIZE) {
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ if ( NwBurstModeEnabled ) {
+
+ pNpScb->BurstRenegotiateReqd = TRUE;
+
+ pNpScb->SourceConnectionId = rand();
+ pNpScb->MaxSendSize = NwMaxSendSize;
+ pNpScb->MaxReceiveSize = NwMaxReceiveSize;
+ pNpScb->BurstSequenceNo = 0;
+ pNpScb->BurstRequestNo = 0;
+
+ Status = ExchangeWithWait(
+ pIrpContext,
+ SynchronousResponseCallback,
+ "FDdWdd",
+ NCP_NEGOTIATE_BURST_CONNECTION,
+ pNpScb->SourceConnectionId,
+ pNpScb->BufferSize,
+ pNpScb->Burst.Socket,
+ pNpScb->MaxSendSize,
+ pNpScb->MaxReceiveSize );
+
+ if ( NT_SUCCESS( Status )) {
+ Status = ParseResponse(
+ pIrpContext,
+ pIrpContext->rsp,
+ pIrpContext->ResponseLength,
+ "Ned",
+ &pNpScb->DestinationConnectionId,
+ &pNpScb->MaxPacketSize );
+
+ if (pNpScb->MaxPacketSize <= DEFAULT_PACKET_SIZE) {
+ pNpScb->MaxPacketSize = DEFAULT_PACKET_SIZE;
+ }
+ }
+
+ if ( NT_SUCCESS( Status )) {
+
+ if (NT_SUCCESS(GetMaxPacketSize( pIrpContext, pNpScb ))) {
+ *LIPNegotiated = TRUE ;
+ }
+
+ pNpScb->SendBurstModeEnabled = TRUE;
+ pNpScb->ReceiveBurstModeEnabled = TRUE;
+
+ //
+ // Use this size as the max read and write size instead of
+ // negotiating. This is what the VLM client does and is
+ // important because the negotiate will give a smaller value.
+ //
+
+ pNpScb->BufferSize = (USHORT)pNpScb->MaxPacketSize;
+
+ return STATUS_SUCCESS;
+ }
+ }
+
+ return STATUS_NOT_SUPPORTED;
+}
+
+
+
+VOID
+RenegotiateBurstMode(
+ PIRP_CONTEXT pIrpContext,
+ PNONPAGED_SCB pNpScb
+ )
+/*++
+
+Routine Description:
+
+ This routine renegotiates a burst mode connection with the specified
+ server. I don't know why we need this but it seems to be required
+ by Netware latest burst implementation.
+
+Arguments:
+
+ pIrpContext - Supplies context and server information.
+
+ pNpScb - A pointer to the NONPAGED_SCB for the server we are
+ negotiating with.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PAGED_CODE();
+
+ DebugTrace( 0, DEBUG_TRACE_LIP, "Re-negotiating burst mode.\n", 0);
+
+ pNpScb->SourceConnectionId = rand();
+ pNpScb->MaxSendSize = NwMaxSendSize;
+ pNpScb->MaxReceiveSize = NwMaxReceiveSize;
+ pNpScb->BurstSequenceNo = 0;
+ pNpScb->BurstRequestNo = 0;
+
+ Status = ExchangeWithWait(
+ pIrpContext,
+ SynchronousResponseCallback,
+ "FDdWdd",
+ NCP_NEGOTIATE_BURST_CONNECTION,
+ pNpScb->SourceConnectionId,
+ pNpScb->MaxPacketSize,
+ pNpScb->Burst.Socket,
+ pNpScb->MaxSendSize,
+ pNpScb->MaxReceiveSize );
+
+ if ( NT_SUCCESS( Status )) {
+ Status = ParseResponse(
+ pIrpContext,
+ pIrpContext->rsp,
+ pIrpContext->ResponseLength,
+ "Ned",
+ &pNpScb->DestinationConnectionId,
+ &pNpScb->MaxPacketSize );
+
+ //
+ // Randomly downgrade the max burst size, because that is what
+ // the netware server does, and the new burst NLM requires.
+ //
+
+ pNpScb->MaxPacketSize -= 66;
+
+ }
+
+ if ( !NT_SUCCESS( Status ) ||
+ (pNpScb->MaxPacketSize <= DEFAULT_PACKET_SIZE)) {
+
+ pNpScb->MaxPacketSize = DEFAULT_PACKET_SIZE;
+ pNpScb->SendBurstModeEnabled = FALSE;
+ pNpScb->ReceiveBurstModeEnabled = FALSE;
+
+ } else {
+
+ //
+ // Use this size as the max read and write size instead of
+ // negotiating. This is what the VLM client does and is
+ // important because the negotiate will give a smaller value.
+ //
+
+ pNpScb->BufferSize = (USHORT)pNpScb->MaxPacketSize;
+
+ }
+}
+
+
+NTSTATUS
+GetMaxPacketSize(
+ PIRP_CONTEXT pIrpContext,
+ PNONPAGED_SCB pNpScb
+ )
+/*++
+
+Routine Description:
+
+ This routine attempts to use the LIP protocol to find the true MTU of
+ the network.
+
+Arguments:
+
+ pIrpContext - Supplies context and server information.
+
+ pNpScb - A pointer to the NONPAGED_SCB for the server we are '
+ negotiating with.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PUSHORT Buffer = NULL;
+ int index, value;
+ PMDL PartialMdl = NULL, FullMdl = NULL;
+ PMDL ReceiveMdl;
+ NTSTATUS Status;
+ USHORT EchoSocket, LipPacketSize = 0;
+ int MinPacketSize, MaxPacketSize, CurrentPacketSize;
+ ULONG RxMdlLength = MdlLength(pIrpContext->RxMdl); // Save so we can restore it on exit.
+
+ BOOLEAN SecondTime = FALSE;
+ LARGE_INTEGER StartTime, Now, FirstPing, SecondPing, temp;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, DEBUG_TRACE_LIP, "GetMaxPacketSize...\n", 0);
+
+ //
+ // Negotiate LIP, attempt to negotiate a buffer of full network
+ // size.
+ //
+
+ Status = ExchangeWithWait(
+ pIrpContext,
+ SynchronousResponseCallback,
+ "Fwb",
+ NCP_NEGOTIATE_LIP_CONNECTION,
+ pNpScb->BufferSize,
+ 0 ); // Flags
+
+ if ( NT_SUCCESS( Status )) {
+ Status = ParseResponse(
+ pIrpContext,
+ pIrpContext->rsp,
+ pIrpContext->ResponseLength,
+ "Nwx",
+ &LipPacketSize,
+ &EchoSocket );
+ }
+
+ //
+ // Speedup RAS
+ //
+
+ MaxPacketSize = (int) LipPacketSize - LipPacketAdjustment ;
+
+ if (( !NT_SUCCESS( Status )) ||
+ ( MaxPacketSize <= DEFAULT_PACKET_SIZE ) ||
+ ( EchoSocket == 0 )) {
+
+ //
+ // The server does not support LIP.
+ // Portable NW gives no error but socket 0.
+ // We have a report of a 3.11 server returning MaxPacketSize 0
+ //
+
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ //
+ // Account for the IPX header, which is not counted in
+ // the reported packet size. This causes problems for
+ // servers with poorly written net card drivers that
+ // abend when they get an oversize packet.
+ //
+ // This was reported by Richard Florance (richfl).
+ //
+
+ MaxPacketSize -= 30;
+
+ pNpScb->EchoCounter = MaxPacketSize;
+
+ //
+ // We will use the Echo address for the LIP protocol.
+ //
+
+ BuildIpxAddress(
+ pNpScb->ServerAddress.Net,
+ pNpScb->ServerAddress.Node,
+ EchoSocket,
+ &pNpScb->EchoAddress );
+
+ try {
+
+ Buffer = ALLOCATE_POOL_EX( NonPagedPool, MaxPacketSize );
+
+ //
+ // Avoid RAS compression algorithm from making the large and small
+ // buffers the same length since we want to see the difference in
+ // transmission times.
+ //
+
+ for (index = 0, value = 0; index < MaxPacketSize/2; index++, value++) {
+ Buffer[index] = value;
+ }
+
+ FullMdl = ALLOCATE_MDL( Buffer, MaxPacketSize, TRUE, FALSE, NULL );
+ if ( FullMdl == NULL ) {
+ ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
+ }
+
+ PartialMdl = ALLOCATE_MDL( Buffer, MaxPacketSize, TRUE, FALSE, NULL );
+ if ( PartialMdl == NULL ) {
+ ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
+ }
+
+ ReceiveMdl = ALLOCATE_MDL( Buffer, MaxPacketSize, TRUE, FALSE, NULL );
+ if ( ReceiveMdl == NULL ) {
+ ExRaiseStatus( STATUS_INSUFFICIENT_RESOURCES );
+ }
+
+ } except( NwExceptionFilter( pIrpContext->pOriginalIrp, GetExceptionInformation() )) {
+
+ if ( Buffer != NULL ) {
+ FREE_POOL( Buffer );
+ }
+
+ if ( FullMdl != NULL ) {
+ FREE_MDL( FullMdl );
+ }
+
+ if ( PartialMdl != NULL ) {
+ FREE_MDL( FullMdl );
+ }
+
+ return STATUS_NOT_SUPPORTED;
+ }
+
+ MmBuildMdlForNonPagedPool( FullMdl );
+
+ //
+ // Allocate a receive MDL and chain in to the IrpContext receive MDL.
+ //
+
+ pIrpContext->RxMdl->ByteCount = sizeof( NCP_RESPONSE ) + sizeof(ULONG);
+ MmBuildMdlForNonPagedPool( ReceiveMdl );
+ pIrpContext->RxMdl->Next = ReceiveMdl;
+
+ CurrentPacketSize = MaxPacketSize;
+ MinPacketSize = DEFAULT_PACKET_SIZE;
+
+ // Log values before we update them.
+ DebugTrace( 0, DEBUG_TRACE_LIP, "Using TickCount = %08lx\n", pNpScb->TickCount * pNpScb->MaxPacketSize);
+ DebugTrace( 0, DEBUG_TRACE_LIP, "pNpScb->NwSendDelay = %08lx\n", pNpScb->NwSendDelay );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "pNpScb->NtSendDelay H = %08lx\n", pNpScb->NtSendDelay.HighPart );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "pNpScb->NtSendDelay L = %08lx\n", pNpScb->NtSendDelay.LowPart );
+
+ //
+ // Loop using the bisection method to find the maximum packet size. Feel free to
+ // use shortcuts to avoid unnecessary timeouts.
+ //
+
+ while (TRUE) {
+
+ DebugTrace( 0, DEBUG_TRACE_LIP, "Sending %d byte echo\n", CurrentPacketSize );
+
+ IoBuildPartialMdl(
+ FullMdl,
+ PartialMdl,
+ Buffer,
+ CurrentPacketSize - sizeof(NCP_RESPONSE) - sizeof(ULONG) );
+
+ //
+ // Send an echo packet. If we get a response, then we know that
+ // the minimum packet size we can use is at least as big as the
+ // echo packet size.
+ //
+
+ pIrpContext->pTdiStruct = &pIrpContext->pNpScb->Echo;
+
+ //
+ // Short-circuit the even better RAS compression.
+ //
+
+ for ( index = 0; index < MaxPacketSize/2; index++, value++) {
+ Buffer[index] = value;
+ }
+
+ KeQuerySystemTime( &StartTime );
+
+ Status = ExchangeWithWait(
+ pIrpContext,
+ SynchronousResponseCallback,
+ "E_Df",
+ sizeof(NCP_RESPONSE ),
+ pNpScb->EchoCounter,
+ PartialMdl );
+
+ if (( Status != STATUS_REMOTE_NOT_LISTENING ) ||
+ ( SecondTime )) {
+
+ KeQuerySystemTime( &Now );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "Response received %08lx\n", Status);
+
+ if (!SecondTime) {
+
+ MinPacketSize = CurrentPacketSize;
+ FirstPing.QuadPart = Now.QuadPart - StartTime.QuadPart;
+ }
+
+ } else {
+
+ DebugTrace( 0, DEBUG_TRACE_LIP, "No response\n", 0);
+ MaxPacketSize = CurrentPacketSize;
+ }
+
+ pNpScb->EchoCounter++;
+ MmPrepareMdlForReuse( PartialMdl );
+
+
+ if (( MaxPacketSize - MinPacketSize <= LipAccuracy ) ||
+ ( SecondTime )) {
+
+ //
+ // We have the maximum packet size.
+ // Now - StartTime is how long it takes for the round-trip. Now we'll
+ // try the same thing with a small packet and see how long it takes. From
+ // this we'll derive a throughput rating.
+ //
+
+
+ if ( SecondTime) {
+
+ SecondPing.QuadPart = Now.QuadPart - StartTime.QuadPart;
+ break;
+
+ } else {
+ SecondTime = TRUE;
+ // Use a small packet size to verify that the server is still up.
+ CurrentPacketSize = sizeof(NCP_RESPONSE) + sizeof(ULONG) * 2;
+ }
+
+ } else {
+
+ //
+ // Calculate the next packet size guess.
+ //
+
+ if (( Status == STATUS_REMOTE_NOT_LISTENING ) &&
+ ( MaxPacketSize == 1463 )) {
+
+ CurrentPacketSize = 1458;
+
+ } else if (( Status == STATUS_REMOTE_NOT_LISTENING ) &&
+ ( MaxPacketSize == 1458 )) {
+
+ CurrentPacketSize = 1436;
+
+ } else {
+
+ //
+ // We didn't try one of our standard sizes so use the chop search
+ // to get to the next value.
+ //
+
+ CurrentPacketSize = ( MaxPacketSize + MinPacketSize ) / 2;
+
+ }
+ }
+ }
+
+ DebugTrace( 0, DEBUG_TRACE_LIP, "Set maximum burst packet size to %d\n", MinPacketSize );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "FirstPing H = %08lx\n", FirstPing.HighPart );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "FirstPing L = %08lx\n", FirstPing.LowPart );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "SecondPing H = %08lx\n", SecondPing.HighPart );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "SecondPing L = %08lx\n", SecondPing.LowPart );
+
+ //
+ // Avoid a divide by zero error if something bad happened.
+ //
+
+ if ( FirstPing.QuadPart != 0 ) {
+ pNpScb->LipDataSpeed = (ULONG) ( ( (LONGLONG)MinPacketSize * (LONGLONG)1600000 )
+ / FirstPing.QuadPart );
+ } else {
+ pNpScb->LipDataSpeed = 0;
+ }
+
+ DebugTrace( 0, DEBUG_TRACE_LIP, "LipDataSpeed: %d\n", pNpScb->LipDataSpeed );
+
+ if ((NT_SUCCESS(Status)) &&
+ ( MinPacketSize > DEFAULT_PACKET_SIZE )) {
+
+ temp.QuadPart = FirstPing.QuadPart - SecondPing.QuadPart;
+
+ if (temp.QuadPart > 0) {
+
+ //
+ // Convert to single trip instead of both ways.
+ //
+
+ temp.QuadPart = temp.QuadPart / (2 * 1000);
+
+ } else {
+
+ //
+ // Small packet ping is slower or the same speed as the big ping.
+ // We can't time a small enough interval so go for no delay at all.
+ //
+
+ temp.QuadPart = 0;
+
+ }
+
+
+ ASSERT(temp.HighPart == 0);
+
+ pNpScb->NwGoodSendDelay = pNpScb->NwBadSendDelay = pNpScb->NwSendDelay =
+ MAX(temp.LowPart, (ULONG)MinSendDelay);
+
+ pNpScb->NwGoodReceiveDelay = pNpScb->NwBadReceiveDelay = pNpScb->NwReceiveDelay =
+ MAX(temp.LowPart, (ULONG)MinReceiveDelay);
+
+ //
+ // Time for a big packet to go one way.
+ //
+
+ pNpScb->NwSingleBurstPacketTime = pNpScb->NwReceiveDelay;
+
+ pNpScb->NtSendDelay.QuadPart = pNpScb->NwReceiveDelay * -1000;
+
+
+ //
+ // Maximum that SendDelay is allowed to reach
+ //
+
+ pNpScb->NwMaxSendDelay = MAX( 52, MIN( pNpScb->NwSendDelay, MaxSendDelay ));
+ pNpScb->NwMaxReceiveDelay = MAX( 52, MIN( pNpScb->NwReceiveDelay, MaxReceiveDelay ));
+
+ //
+ // Time for a small packet to get to the server and back.
+ //
+
+ temp.QuadPart = SecondPing.QuadPart / 1000;
+ pNpScb->NwLoopTime = temp.LowPart;
+
+ DebugTrace( 0, DEBUG_TRACE_LIP, "Using TickCount = %08lx\n", pNpScb->TickCount * pNpScb->MaxPacketSize);
+ DebugTrace( 0, DEBUG_TRACE_LIP, "pNpScb->NwSendDelay = %08lx\n", pNpScb->NwSendDelay );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "pNpScb->NwMaxSendDelay = %08lx\n", pNpScb->NwMaxSendDelay );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "pNpScb->NwMaxReceiveDelay = %08lx\n", pNpScb->NwMaxReceiveDelay );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "pNpScb->NwLoopTime = %08lx\n", pNpScb->NwLoopTime );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "pNpScb->NtSendDelay H = %08lx\n", pNpScb->NtSendDelay.HighPart );
+ DebugTrace( 0, DEBUG_TRACE_LIP, "pNpScb->NtSendDelay L = %08lx\n", pNpScb->NtSendDelay.LowPart );
+
+ //
+ // Reset Tdi struct so that we send future NCPs from the server socket.
+ //
+
+ pIrpContext->pTdiStruct = NULL;
+
+ //
+ // Now decouple the MDL
+ //
+
+ pIrpContext->TxMdl->Next = NULL;
+ pIrpContext->RxMdl->Next = NULL;
+ pIrpContext->RxMdl->ByteCount = RxMdlLength;
+
+ //
+ // Calculate the maximum amount of data we can send in a burst write
+ // packet after all the header info is stripped.
+ //
+ // BUGBUG - This is what Novell does, but real header isn't that big
+ // can we do better?
+ //
+
+ pNpScb->MaxPacketSize = MinPacketSize - sizeof( NCP_BURST_WRITE_REQUEST );
+
+ FREE_MDL( PartialMdl );
+ FREE_MDL( ReceiveMdl );
+ FREE_MDL( FullMdl );
+ FREE_POOL( Buffer );
+
+
+ DebugTrace( -1, DEBUG_TRACE_LIP, "GetMaxPacketSize -> VOID\n", 0);
+ return STATUS_SUCCESS;
+
+ } else {
+
+ //
+ // If the small packet couldn't echo then assume the worst.
+ //
+
+ //
+ // Reset Tdi struct so that we send future NCPs from the server socket.
+ //
+
+ pIrpContext->pTdiStruct = NULL;
+
+ //
+ // Now decouple the MDL
+ //
+
+ pIrpContext->TxMdl->Next = NULL;
+ pIrpContext->RxMdl->Next = NULL;
+ pIrpContext->RxMdl->ByteCount = RxMdlLength;
+
+ FREE_MDL( PartialMdl );
+ FREE_MDL( ReceiveMdl );
+ FREE_MDL( FullMdl );
+ FREE_POOL( Buffer );
+
+
+ DebugTrace( -1, DEBUG_TRACE_LIP, "GetMaxPacketSize -> VOID\n", 0);
+ return STATUS_NOT_SUPPORTED;
+ }
+
+}
+
+
+VOID
+DestroyAllScb(
+ VOID
+ )
+
+/*++
+
+Routine Description:
+
+ This routine destroys all server control blocks.
+
+Arguments:
+
+
+Return Value:
+
+
+--*/
+
+{
+ KIRQL OldIrql;
+ PLIST_ENTRY ScbQueueEntry;
+ PNONPAGED_SCB pNpScb;
+
+ DebugTrace(+1, Dbg, "DestroyAllScbs....\n", 0);
+
+ KeAcquireSpinLock(&ScbSpinLock, &OldIrql);
+
+ //
+ // Walk the list of SCBs and kill them all.
+ //
+
+ while (!IsListEmpty(&ScbQueue)) {
+
+ ScbQueueEntry = RemoveHeadList( &ScbQueue );
+ pNpScb = CONTAINING_RECORD(ScbQueueEntry, NONPAGED_SCB, ScbLinks);
+
+ //
+ // We can't hold the spin lock while deleting an SCB, so release
+ // it now.
+ //
+
+ KeReleaseSpinLock(&ScbSpinLock, OldIrql);
+
+ NwDeleteScb( pNpScb->pScb );
+
+ KeAcquireSpinLock(&ScbSpinLock, &OldIrql);
+ }
+
+ KeReleaseSpinLock(&ScbSpinLock, OldIrql);
+
+ DebugTrace(-1, Dbg, "DestroyAllScb\n", 0 );
+}
+
+
+VOID
+NwDeleteScb(
+ PSCB pScb
+ )
+/*++
+
+Routine Description:
+
+ This routine deletes an SCB. The SCB must not be in use.
+
+ *** The caller must own the RCB exclusive.
+
+Arguments:
+
+ Scb - The SCB to delete
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PNONPAGED_SCB pNpScb;
+ BOOLEAN AnonymousScb = IS_ANONYMOUS_SCB( pScb );
+
+ PAGED_CODE();
+
+ DebugTrace(+1, Dbg, "NwDeleteScb...\n", 0);
+
+ pNpScb = pScb->pNpScb;
+
+ //
+ // Make sure we are not deleting a logged in connection
+ // or we will hang up the license until the server times
+ // it out.
+ //
+
+ ASSERT( pNpScb->State != SCB_STATE_IN_USE );
+ ASSERT( pNpScb->Reference == 0 );
+ ASSERT( !pNpScb->Sending );
+ ASSERT( !pNpScb->Receiving );
+ ASSERT( !pNpScb->OkToReceive );
+ ASSERT( IsListEmpty( &pNpScb->Requests ) );
+ ASSERT( IsListEmpty( &pScb->IcbList ) );
+ ASSERT( pScb->IcbCount == 0 );
+ ASSERT( pScb->VcbCount == 0 );
+
+
+ DebugTrace(0, Dbg, "Cleaning up SCB %08lx\n", pScb);
+
+ if ( AnonymousScb ) {
+ DebugTrace(0, Dbg, "SCB is anonymous\n", &pNpScb->ServerName );
+ } else {
+ ASSERT( IsListEmpty( &pScb->ScbSpecificVcbQueue ) );
+ DebugTrace(0, Dbg, "SCB is %wZ\n", &pNpScb->ServerName );
+ }
+
+ DebugTrace(0, Dbg, "SCB state is %d\n", &pNpScb->State );
+
+ if ( !AnonymousScb ) {
+ RtlRemoveUnicodePrefix ( &NwRcb.ServerNameTable, &pScb->PrefixEntry );
+ }
+
+ IPX_Close_Socket( &pNpScb->Server );
+ IPX_Close_Socket( &pNpScb->WatchDog );
+ IPX_Close_Socket( &pNpScb->Send );
+ IPX_Close_Socket( &pNpScb->Echo);
+ IPX_Close_Socket( &pNpScb->Burst);
+
+ FREE_POOL( pNpScb );
+
+ if ( pScb->UserName.Buffer != NULL ) {
+ FREE_POOL( pScb->UserName.Buffer );
+ }
+
+ FREE_POOL( pScb );
+
+ DebugTrace(-1, Dbg, "NwDeleteScb -> VOID\n", 0);
+}
+
+
+PNONPAGED_SCB
+SelectConnection(
+ PNONPAGED_SCB NpScb OPTIONAL
+ )
+/*++
+
+Routine Description:
+
+ Find a default server (which is also the nearest server).
+ If NpScb is not supplied, simply return the first server in
+ the list. If it is supplied return the next server in the
+ list after the given server.
+
+Arguments:
+
+ NpScb - The starting point for the server search.
+
+Return Value:
+
+ Scb to be used or NULL.
+
+--*/
+{
+ PLIST_ENTRY ScbQueueEntry;
+ KIRQL OldIrql;
+ PNONPAGED_SCB pNextNpScb;
+
+ DebugTrace(+1, Dbg, "SelectConnection....\n", 0);
+ KeAcquireSpinLock(&ScbSpinLock, &OldIrql);
+
+ if ( NpScb == NULL ) {
+ ScbQueueEntry = ScbQueue.Flink ;
+ } else {
+ ScbQueueEntry = NpScb->ScbLinks.Flink;
+ }
+
+ for ( ;
+ ScbQueueEntry != &ScbQueue ;
+ ScbQueueEntry = ScbQueueEntry->Flink ) {
+
+ pNextNpScb = CONTAINING_RECORD(
+ ScbQueueEntry,
+ NONPAGED_SCB,
+ ScbLinks );
+
+ //
+ // Check to make sure that this SCB is usable.
+ //
+
+ if (( pNextNpScb->State == SCB_STATE_RECONNECT_REQUIRED ) ||
+ ( pNextNpScb->State == SCB_STATE_LOGIN_REQUIRED ) ||
+ ( pNextNpScb->State == SCB_STATE_IN_USE )) {
+
+ NwReferenceScb( pNextNpScb );
+
+ KeReleaseSpinLock(&ScbSpinLock, OldIrql);
+ DebugTrace(+0, Dbg, " NpScb = %lx\n", pNextNpScb );
+ DebugTrace(-1, Dbg, " NpScb->State = %x\n", pNextNpScb->State );
+ return pNextNpScb;
+ }
+ }
+
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql);
+ DebugTrace(-1, Dbg, " NULL\n", 0);
+ return NULL;
+}
+
+
+VOID
+NwLogoffAllServers(
+ PIRP_CONTEXT pIrpContext,
+ PLARGE_INTEGER Uid
+ )
+/*++
+
+Routine Description:
+
+ This routine sends a logoff to all connected servers created by the Logon
+ user or all servers if Logon is NULL.
+
+Arguments:
+
+ Uid - Supplies the servers to disconnect from.
+
+Return Value:
+
+ none.
+
+--*/
+{
+ KIRQL OldIrql;
+ PLIST_ENTRY ScbQueueEntry;
+ PLIST_ENTRY NextScbQueueEntry;
+ PNONPAGED_SCB pNpScb;
+
+ DebugTrace( 0, Dbg, "NwLogoffAllServers\n", 0 );
+
+ KeAcquireSpinLock( &ScbSpinLock, &OldIrql );
+
+ for (ScbQueueEntry = ScbQueue.Flink ;
+ ScbQueueEntry != &ScbQueue ;
+ ScbQueueEntry = NextScbQueueEntry ) {
+
+ pNpScb = CONTAINING_RECORD( ScbQueueEntry, NONPAGED_SCB, ScbLinks );
+
+ //
+ // Reference the SCB so that it doesn't disappear while we
+ // are disconnecting.
+ //
+
+ NwReferenceScb( pNpScb );
+
+ //
+ // Release the SCB spin lock so that we can send a logoff
+ // NCP.
+ //
+
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql );
+
+ //
+ // Destroy this Scb if its not the permanent Scb and either we
+ // are destroying all Scb's or it was created for this user.
+ //
+
+ if (( pNpScb->pScb != NULL ) &&
+ (( Uid == NULL ) ||
+ ( pNpScb->pScb->UserUid.QuadPart == (*Uid).QuadPart))) {
+
+ NwLogoffAndDisconnect( pIrpContext, pNpScb );
+ }
+
+ KeAcquireSpinLock( &ScbSpinLock, &OldIrql );
+
+ //
+ // Release the temporary reference.
+ //
+
+ NextScbQueueEntry = pNpScb->ScbLinks.Flink;
+ NwDereferenceScb( pNpScb );
+ }
+
+ KeReleaseSpinLock( &ScbSpinLock, OldIrql );
+}
+
+
+VOID
+NwLogoffAndDisconnect(
+ PIRP_CONTEXT pIrpContext,
+ PNONPAGED_SCB pNpScb
+ )
+/*++
+
+Routine Description:
+
+ This routine sends a logoff and disconnects from the name server.
+
+Arguments:
+
+ pIrpContext - A pointer to the current IRP context.
+ pNpScb - A pointer to the server to logoff and disconnect.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ PSCB pScb = pNpScb->pScb;
+
+ PAGED_CODE();
+
+ pIrpContext->pNpScb = pNpScb;
+ pIrpContext->pScb = pScb;
+
+ //
+ // Queue ourselves to the SCB, and wait to get to the front to
+ // protect access to server State.
+ //
+
+ NwAppendToQueueAndWait( pIrpContext );
+
+ //
+ // If we are logging out from the preferred server, free the preferred
+ // server reference.
+ //
+
+ if ( pScb != NULL &&
+ pScb->PreferredServer ) {
+ pScb->PreferredServer = FALSE;
+ NwDereferenceScb( pNpScb );
+ }
+
+ //
+ // Nothing to do if we are not connected.
+ //
+
+ if ( pNpScb->State != SCB_STATE_IN_USE &&
+ pNpScb->State != SCB_STATE_LOGIN_REQUIRED ) {
+
+ NwDequeueIrpContext( pIrpContext, FALSE );
+ return;
+ }
+
+ //
+ // If we timeout then we don't want to go to the bother of
+ // reconnecting.
+ //
+
+ ClearFlag( pIrpContext->Flags, IRP_FLAG_RECONNECTABLE );
+
+ //
+ // Logout and disconnect.
+ //
+
+ if ( pNpScb->State == SCB_STATE_IN_USE ) {
+
+ ExchangeWithWait (
+ pIrpContext,
+ SynchronousResponseCallback,
+ "F",
+ NCP_LOGOUT );
+ }
+
+ ExchangeWithWait (
+ pIrpContext,
+ SynchronousResponseCallback,
+ "D-" ); // Disconnect
+
+ Stats.Sessions--;
+
+ if ( pScb->MajorVersion == 2 ) {
+ Stats.NW2xConnects--;
+ } else if ( pScb->MajorVersion == 3 ) {
+ Stats.NW3xConnects--;
+ } else if ( pScb->MajorVersion == 4 ) {
+ Stats.NW4xConnects--;
+ }
+
+ //
+ // Free the remembered username and password.
+ //
+
+ if ( pScb != NULL && pScb->UserName.Buffer != NULL ) {
+ FREE_POOL( pScb->UserName.Buffer );
+ RtlInitUnicodeString( &pScb->UserName, NULL );
+ RtlInitUnicodeString( &pScb->Password, NULL );
+ }
+
+ pNpScb->State = SCB_STATE_RECONNECT_REQUIRED;
+
+ NwDequeueIrpContext( pIrpContext, FALSE );
+ return;
+}
+
+
+VOID
+InitializeAttach (
+ VOID
+ )
+/*++
+
+Routine Description:
+
+ Initialize global structures for attaching to servers.
+
+Arguments:
+
+ none.
+
+Return Value:
+
+ none.
+
+--*/
+{
+ PAGED_CODE();
+
+ KeInitializeSpinLock( &ScbSpinLock );
+ InitializeListHead(&ScbQueue);
+}
+
+
+NTSTATUS
+OpenScbSockets(
+ PIRP_CONTEXT pIrpContext,
+ PNONPAGED_SCB pNpScb
+ )
+/*++
+
+Routine Description:
+
+ Open the communications sockets for an SCB.
+
+Arguments:
+
+ pIrpContext - The IRP context pointers for the request in progress.
+
+ pNpScb - The SCB to connect to the network.
+
+Return Value:
+
+ The status of the operation.
+
+--*/
+{
+ NTSTATUS Status;
+
+ PAGED_CODE();
+
+ //
+ // Auto allocate to the server socket.
+ //
+
+ pNpScb->Server.Socket = 0;
+
+ Status = IPX_Open_Socket (pIrpContext, &pNpScb->Server);
+
+ if ( !NT_SUCCESS(Status) ) {
+ return( Status );
+ }
+
+ //
+ // Watchdog Socket is Server.Socket+1
+ //
+
+ pNpScb->WatchDog.Socket = NextSocket( pNpScb->Server.Socket );
+ Status = IPX_Open_Socket ( pIrpContext, &pNpScb->WatchDog );
+
+ if ( !NT_SUCCESS(Status) ) {
+ return( Status );
+ }
+
+ //
+ // Send Socket is WatchDog.Socket+1
+ //
+
+ pNpScb->Send.Socket = NextSocket( pNpScb->WatchDog.Socket );
+ Status = IPX_Open_Socket ( pIrpContext, &pNpScb->Send );
+
+ if ( !NT_SUCCESS(Status) ) {
+ return( Status );
+ }
+
+ //
+ // Echo socket
+ //
+
+ pNpScb->Echo.Socket = NextSocket( pNpScb->Send.Socket );
+ Status = IPX_Open_Socket ( pIrpContext, &pNpScb->Echo );
+
+ if ( !NT_SUCCESS(Status) ) {
+ return( Status );
+ }
+
+ //
+ // Burst socket
+ //
+
+ pNpScb->Burst.Socket = NextSocket( pNpScb->Echo.Socket );
+ Status = IPX_Open_Socket ( pIrpContext, &pNpScb->Burst );
+
+ if ( !NT_SUCCESS(Status) ) {
+ return( Status );
+ }
+
+ return( STATUS_SUCCESS );
+}
+
+NTSTATUS
+DoBinderyLogon(
+ IN PIRP_CONTEXT IrpContext,
+ IN PUNICODE_STRING UserName,
+ IN PUNICODE_STRING Password
+ )
+/*++
+
+Routine Description:
+
+ Performs a bindery based encrypted logon.
+
+ Note: Rcb is held exclusively so that we can access the Logon queue
+ safely.
+
+Arguments:
+
+ pIrpContext - The IRP context pointers for the request in progress.
+
+ UserName - The user name to use to login.
+
+ Password - The password to use to login.
+
+Return Value:
+
+ The status of the operation.
+
+--*/
+{
+ PNONPAGED_SCB pNpScb;
+ PSCB pScb;
+ UNICODE_STRING Name;
+ UNICODE_STRING PWord;
+ UCHAR EncryptKey[ENCRYPTION_KEY_SIZE ];
+ NTSTATUS Status;
+ PVOID Buffer;
+ PLOGON Logon = NULL;
+ PWCH OldBuffer;
+
+ PAGED_CODE();
+
+ DebugTrace( +1, Dbg, "DoBinderyLogon...\n", 0);
+
+ //
+ // First get an encryption key.
+ //
+
+ DebugTrace( +0, Dbg, " Get Login key\n", 0);
+
+ Status = ExchangeWithWait (
+ IrpContext,
+ SynchronousResponseCallback,
+ "S",
+ NCP_ADMIN_FUNCTION, NCP_GET_LOGIN_KEY );
+
+ pNpScb = IrpContext->pNpScb;
+ pScb = pNpScb->pScb;
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if ( NT_SUCCESS( Status ) ) {
+ Status = ParseResponse(
+ IrpContext,
+ IrpContext->rsp,
+ IrpContext->ResponseLength,
+ "Nr",
+ EncryptKey, sizeof(EncryptKey) );
+ }
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ //
+ // Choose a name and password to use to connect to the server. Use
+ // the user supplied if the exist. Otherwise if the server already
+ // has a remembered username use the remembered name. If nothing
+ // else is available use the defaults from logon. Finally, if the
+ // user didn't even logon, use GUEST no password.
+ //
+
+
+ if ( UserName != NULL && UserName->Buffer != NULL ) {
+
+ Name = *UserName;
+
+ } else if ( pScb->UserName.Buffer != NULL ) {
+
+ Name = pScb->UserName;
+
+ } else {
+
+ Logon = FindUser( &pScb->UserUid, FALSE );
+
+ if (Logon != NULL ) {
+ Name = Logon->UserName;
+ } else {
+ ASSERT( FALSE && "No logon record found" );
+ return( STATUS_ACCESS_DENIED );
+ }
+ }
+
+ if ( Password != NULL && Password->Buffer != NULL ) {
+
+ PWord = *Password;
+
+ } else if ( pScb->Password.Buffer != NULL ) {
+
+ PWord = pScb->Password;
+
+ } else {
+
+ if ( Logon == NULL ) {
+ Logon = FindUser( &pScb->UserUid, FALSE );
+ }
+
+ if ( Logon != NULL ) {
+ PWord = Logon->PassWord;
+ } else {
+ ASSERT( FALSE && "No logon record found" );
+ return( STATUS_ACCESS_DENIED );
+ }
+ }
+
+
+ if ( !NT_SUCCESS(Status) ) {
+
+ //
+ // Failed to get an encryption key. Login to server, plain text
+ //
+
+ DebugTrace( +0, Dbg, " Plain Text Login\n", 0);
+
+ Status = ExchangeWithWait (
+ IrpContext,
+ SynchronousResponseCallback,
+ "SwUU",
+ NCP_ADMIN_FUNCTION, NCP_PLAIN_TEXT_LOGIN,
+ OT_USER,
+ &Name,
+ &PWord);
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if ( NT_SUCCESS( Status ) ) {
+ Status = ParseResponse(
+ IrpContext,
+ IrpContext->rsp,
+ IrpContext->ResponseLength,
+ "N" );
+ }
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if ( !NT_SUCCESS( Status )) {
+ return( STATUS_WRONG_PASSWORD);
+ }
+
+ } else if ( NT_SUCCESS( Status ) ) {
+
+ //
+ // We have an encryption key. Get the ObjectId
+ //
+
+ UCHAR Response[ENCRYPTION_KEY_SIZE];
+ UCHAR ObjectId[OBJECT_ID_SIZE];
+ OEM_STRING UOPassword;
+
+ DebugTrace( +0, Dbg, " Query users objectid\n", 0);
+
+ Status = ExchangeWithWait (
+ IrpContext,
+ SynchronousResponseCallback,
+ "SwU",
+ NCP_ADMIN_FUNCTION, NCP_QUERY_OBJECT_ID,
+ OT_USER,
+ &Name);
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ //
+ // Save the new address in a local copy so that we can logout.
+ //
+
+ Status = ParseResponse(
+ IrpContext,
+ IrpContext->rsp,
+ IrpContext->ResponseLength,
+ "Nr",
+ ObjectId, OBJECT_ID_SIZE );
+ }
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if (!NT_SUCCESS(Status)) {
+ return( STATUS_NO_SUCH_USER );
+ }
+
+ //
+ // Convert the unicode password to uppercase and then the oem
+ // character set.
+ //
+
+ if ( PWord.Length > 0 ) {
+
+ Status = RtlUpcaseUnicodeStringToOemString( &UOPassword, &PWord, TRUE );
+ if (!NT_SUCCESS(Status)) {
+ return( Status );
+ }
+
+ } else {
+ UOPassword.Buffer = "";
+ UOPassword.Length = UOPassword.MaximumLength = 0;
+ }
+
+ RespondToChallenge( ObjectId, &UOPassword, EncryptKey, Response);
+
+ if ( PWord.Length > 0) {
+ RtlFreeAnsiString( &UOPassword );
+ }
+
+ DebugTrace( +0, Dbg, " Encrypted Login\n", 0);
+
+ Status = ExchangeWithWait (
+ IrpContext,
+ SynchronousResponseCallback,
+ "SrwU",
+ NCP_ADMIN_FUNCTION, NCP_ENCRYPTED_LOGIN,
+ Response, sizeof(Response),
+ OT_USER,
+ &Name);
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ //
+ // Save the new address in a local copy so that we can logout
+ //
+
+ Status = ParseResponse(
+ IrpContext,
+ IrpContext->rsp,
+ IrpContext->ResponseLength,
+ "N" );
+ }
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if ( !NT_SUCCESS( Status )) {
+
+ //
+ // Special case error mappings.
+ //
+
+ if (( Status == STATUS_UNSUCCESSFUL ) ||
+ ( Status == STATUS_UNEXPECTED_NETWORK_ERROR /* 2.2 servers */ )) {
+ Status = STATUS_WRONG_PASSWORD;
+ }
+
+ if ( Status == STATUS_LOCK_NOT_GRANTED ) {
+ Status = STATUS_ACCOUNT_RESTRICTION; // Bindery locked
+ }
+
+ if ( Status == STATUS_DISK_FULL ) {
+#ifdef QFE_BUILD
+ Status = STATUS_TOO_MANY_SESSIONS;
+#else
+ Status = STATUS_REMOTE_SESSION_LIMIT;
+#endif
+ }
+
+ if ( Status == STATUS_FILE_LOCK_CONFLICT ) {
+ Status = STATUS_SHARING_PAUSED;
+ }
+
+ if ( Status == STATUS_NO_MORE_ENTRIES ) {
+ Status = STATUS_NO_SUCH_USER; // No such object on "Login Object Encrypted" NCP.
+ }
+
+ //
+ // Stupid Netware 4.x servers return a different NCP error for
+ // a disabled account (from intruder lockout) on bindery login,
+ // and nwconvert maps this to a dos error. In this special case,
+ // we'll catch it and map it back.
+ //
+
+ if ( ( IrpContext->pNpScb->pScb->MajorVersion >= 4 ) &&
+ ( Status == 0xC001003B ) ) {
+ Status = STATUS_ACCOUNT_DISABLED;
+ }
+
+ return( Status );
+ }
+
+ } else {
+
+ return( Status );
+
+ }
+
+ //
+ // If the Uid is for the system process then the username must be
+ // in the NtGateway group on the server.
+ //
+
+ if ( IrpContext->Specific.Create.UserUid.QuadPart == DefaultLuid.QuadPart) {
+
+ NTSTATUS Status1 ;
+
+ // IsBinderyObjectInSet?
+ Status1 = ExchangeWithWait (
+ IrpContext,
+ SynchronousResponseCallback,
+ "SwppwU",
+ NCP_ADMIN_FUNCTION, NCP_IS_OBJECT_IN_SET,
+ OT_GROUP,
+ "NTGATEWAY",
+ "GROUP_MEMBERS",
+ OT_USER,
+ &Name);
+
+ if ( !NT_SUCCESS( Status1 ) ) {
+ return STATUS_ACCESS_DENIED;
+ }
+
+ }
+
+ //
+ // Success. Save the username & password for reconnect.
+ //
+
+ //
+ // Setup to free the old user name and password buffer.
+ //
+
+ if ( pScb->UserName.Buffer != NULL ) {
+ OldBuffer = pScb->UserName.Buffer;
+ } else {
+ OldBuffer = NULL;
+ }
+
+ Buffer = ALLOCATE_POOL( NonPagedPool, Name.Length + PWord.Length );
+ if ( Buffer == NULL ) {
+ return( STATUS_INSUFFICIENT_RESOURCES );
+ }
+
+ pScb->UserName.Buffer = Buffer;
+ pScb->UserName.Length = pScb->UserName.MaximumLength = Name.Length;
+ RtlMoveMemory( pScb->UserName.Buffer, Name.Buffer, Name.Length );
+
+ pScb->Password.Buffer = (PWCHAR)((PCHAR)Buffer + Name.Length);
+ pScb->Password.Length = pScb->Password.MaximumLength = PWord.Length;
+ RtlMoveMemory( pScb->Password.Buffer, PWord.Buffer, PWord.Length );
+
+ if ( OldBuffer != NULL ) {
+ FREE_POOL( OldBuffer );
+ }
+ return( Status );
+}
+
+NTSTATUS
+NwAllocateAndInitScb(
+ IN PIRP_CONTEXT pIrpContext,
+ IN PUNICODE_STRING UidServerName OPTIONAL,
+ IN PUNICODE_STRING ServerName OPTIONAL,
+ OUT PSCB *ppScb
+)
+/*++
+
+Routine Description:
+
+ This routine returns a pointer to a newly created SCB. If
+ the UidServerName and ServerName are supplied, the SCB name
+ fields are initialized to this name. Otherwise, the name
+ fields are left blank to be filled in later.
+
+ If UidServerName is provided, ServerName MUST also be provided!!
+
+ The returned SCB is NOT filed in the server prefix table since
+ it might not yet have a name.
+
+Return Value:
+
+ The created SCB or NULL.
+
+--*/
+{
+
+ NTSTATUS Status;
+ PSCB pScb = NULL;
+ PNONPAGED_SCB pNpScb = NULL;
+ USHORT ServerNameLength;
+ PLOGON Logon;
+
+ //
+ // Allocate enough space for a credential munged tree name.
+ //
+
+ pScb = ALLOCATE_POOL ( PagedPool,
+ sizeof( SCB ) +
+ ( ( NDS_TREE_NAME_LEN + MAX_NDS_NAME_CHARS + 2 ) * sizeof( WCHAR ) ) );
+
+ if ( !pScb ) {
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+
+ RtlZeroMemory( pScb, sizeof( SCB ) );
+ RtlInitializeBitMap( &pScb->DriveMapHeader, pScb->DriveMap, MAX_DRIVES );
+
+ //
+ // Initialize pointers to ensure cleanup on error case operates
+ // correctly.
+ //
+
+ if ( UidServerName &&
+ UidServerName->Length ) {
+
+ ServerNameLength = UidServerName->Length + sizeof( WCHAR );
+
+ } else {
+
+ ServerNameLength = ( MAX_SERVER_NAME_LENGTH * sizeof( WCHAR ) ) +
+ ( MAX_UNICODE_UID_LENGTH * sizeof( WCHAR ) ) +
+ ( 2 * sizeof( WCHAR ) );
+
+ }
+
+ pScb->pNpScb = ALLOCATE_POOL ( NonPagedPool,
+ sizeof( NONPAGED_SCB ) + ServerNameLength );
+
+ if ( !pScb->pNpScb ) {
+ Status = STATUS_INSUFFICIENT_RESOURCES;
+ goto ExitWithCleanup;
+ }
+
+ RtlZeroMemory( pScb->pNpScb, sizeof( NONPAGED_SCB ) );
+
+ pNpScb = pScb->pNpScb;
+ pNpScb->pScb = pScb;
+
+ //
+ // If we know the server name, copy it to the allocated buffer.
+ // Append a NULL so that we can use the name a nul-terminated string.
+ //
+
+ pScb->UidServerName.Buffer = (PWCHAR)( pScb->pNpScb + 1 );
+ pScb->UidServerName.MaximumLength = ServerNameLength;
+ pScb->UidServerName.Length = 0;
+
+ RtlInitUnicodeString( &(pNpScb->ServerName), NULL );
+
+ if ( UidServerName &&
+ UidServerName->Length ) {
+
+ RtlCopyUnicodeString ( &pScb->UidServerName, UidServerName );
+ pScb->UidServerName.Buffer[ UidServerName->Length / sizeof( WCHAR ) ] = L'\0';
+
+ pScb->UnicodeUid = pScb->UidServerName;
+ pScb->UnicodeUid.Length = UidServerName->Length -
+ ServerName->Length -
+ sizeof(WCHAR);
+
+ //
+ // Make ServerName point partway down the buffer for UidServerName
+ //
+
+ pNpScb->ServerName.Buffer = (PWSTR)((PUCHAR)pScb->UidServerName.Buffer +
+ UidServerName->Length - ServerName->Length);
+
+ pNpScb->ServerName.MaximumLength = ServerName->Length;
+ pNpScb->ServerName.Length = ServerName->Length;
+
+ }
+
+ pScb->NdsTreeName.MaximumLength = NDS_TREE_NAME_LEN * sizeof( WCHAR );
+ pScb->NdsTreeName.Buffer = (PWCHAR)(pScb + 1);
+
+ pScb->NodeTypeCode = NW_NTC_SCB;
+ pScb->NodeByteSize = sizeof(SCB);
+ InitializeListHead( &pScb->ScbSpecificVcbQueue );
+ InitializeListHead( &pScb->IcbList );
+
+ //
+ // Remember UID of the file creator so we can find the username and
+ // password to use for this Scb when we need it.
+ //
+
+ pScb->UserUid = pIrpContext->Specific.Create.UserUid;
+
+ //
+ // Initialize the non-paged part of the SCB.
+ //
+
+ pNpScb->NodeTypeCode = NW_NTC_SCBNP;
+ pNpScb->NodeByteSize = sizeof(NONPAGED_SCB);
+
+ //
+ // Set the initial SCB reference count.
+ //
+
+ if ( UidServerName &&
+ UidServerName->Length ) {
+
+ Logon = FindUser( &pScb->UserUid, FALSE );
+
+ if (( Logon != NULL) &&
+ (RtlCompareUnicodeString( ServerName, &Logon->ServerName, TRUE ) == 0 )) {
+ pScb->PreferredServer = TRUE;
+ }
+ }
+
+ if ( pScb->PreferredServer ) {
+ pNpScb->Reference = 2;
+ } else {
+ pNpScb->Reference = 1;
+ }
+
+ //
+ // Finish linking the two parts of the Scb together.
+ //
+
+ pNpScb->pScb = pScb;
+
+ KeInitializeSpinLock( &pNpScb->NpScbSpinLock );
+ KeInitializeSpinLock( &pNpScb->NpScbInterLock );
+ InitializeListHead( &pNpScb->Requests );
+
+ RtlFillMemory( &pNpScb->LocalAddress, sizeof(IPXaddress), 0xff);
+
+ pNpScb->State = SCB_STATE_ATTACHING;
+ pNpScb->SequenceNo = 1;
+
+ Status = OpenScbSockets( pIrpContext, pNpScb );
+ if ( !NT_SUCCESS(Status) ) {
+ goto ExitWithCleanup;
+ }
+
+ Status = SetEventHandler (
+ pIrpContext,
+ &pNpScb->Server,
+ TDI_EVENT_RECEIVE_DATAGRAM,
+ &ServerDatagramHandler,
+ pNpScb );
+
+ if ( !NT_SUCCESS(Status) ) {
+ goto ExitWithCleanup;
+ }
+
+ Status = SetEventHandler (
+ pIrpContext,
+ &pNpScb->WatchDog,
+ TDI_EVENT_RECEIVE_DATAGRAM,
+ &WatchDogDatagramHandler,
+ pNpScb );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto ExitWithCleanup;
+ }
+
+ Status = SetEventHandler (
+ pIrpContext,
+ &pNpScb->Send,
+ TDI_EVENT_RECEIVE_DATAGRAM,
+ &SendDatagramHandler,
+ pNpScb );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto ExitWithCleanup;
+ }
+
+ Status = SetEventHandler (
+ pIrpContext,
+ &pNpScb->Echo,
+ TDI_EVENT_RECEIVE_DATAGRAM,
+ &ServerDatagramHandler,
+ pNpScb );
+
+ pNpScb->EchoCounter = 2;
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto ExitWithCleanup;
+ }
+
+ Status = SetEventHandler (
+ pIrpContext,
+ &pNpScb->Burst,
+ TDI_EVENT_RECEIVE_DATAGRAM,
+ &ServerDatagramHandler,
+ pNpScb );
+
+ if ( !NT_SUCCESS( Status ) ) {
+ goto ExitWithCleanup;
+ }
+
+ KeQuerySystemTime( &pNpScb->LastUsedTime );
+
+ //
+ // Set burst mode data.
+ //
+
+ pNpScb->BurstRequestNo = 0;
+ pNpScb->BurstSequenceNo = 0;
+
+ if ( ppScb ) {
+ *ppScb = pScb;
+ }
+
+ return STATUS_SUCCESS;
+
+ExitWithCleanup:
+
+ if ( pNpScb != NULL ) {
+
+ IPX_Close_Socket( &pNpScb->Server );
+ IPX_Close_Socket( &pNpScb->WatchDog );
+ IPX_Close_Socket( &pNpScb->Send );
+ IPX_Close_Socket( &pNpScb->Echo );
+ IPX_Close_Socket( &pNpScb->Burst );
+
+ FREE_POOL( pNpScb );
+ }
+
+ FREE_POOL(pScb);
+ return Status;
+
+}
+
+
+BOOLEAN
+NwFindScb(
+ OUT PSCB *Scb,
+ PIRP_CONTEXT IrpContext,
+ PUNICODE_STRING UidServerName,
+ PUNICODE_STRING ServerName
+ )
+/*++
+
+Routine Description:
+
+ This routine returns a pointer to the SCB for the named server.
+ The name is looked up in the SCB table. If it is found, a
+ pointer to the SCB is returned. If none is found an SCB is
+ created and initialized.
+
+ This routine returns with the SCB referenced and the SCB
+ resources held.
+
+Arguments:
+
+ Scb - Returns a pointer to the found / created SCB.
+
+ IrpContext - The IRP context pointers for the request in progress.
+
+ ServerName - The name of the server to find / create.
+
+Return Value:
+
+ TRUE - An old SCB was found.
+
+ FALSE - A new SCB was created, or an attempt to create the SCB failed.
+
+--*/
+{
+ BOOLEAN RcbHeld;
+ PUNICODE_PREFIX_TABLE_ENTRY PrefixEntry;
+ NTSTATUS Status;
+ PSCB pScb = NULL;
+ PNONPAGED_SCB pNpScb = NULL;
+ KIRQL OldIrql;
+ BOOLEAN Success;
+
+ //
+ // Acquire the RCB exclusive to protect the prefix table.
+ // Then lookup the name of this server.
+ //
+
+ NwAcquireExclusiveRcb( &NwRcb, TRUE );
+ RcbHeld = TRUE;
+ PrefixEntry = RtlFindUnicodePrefix( &NwRcb.ServerNameTable, UidServerName, 0 );
+
+ if ( PrefixEntry != NULL ) {
+
+ PSCB pScb = NULL;
+ PNONPAGED_SCB pNpScb = NULL;
+
+ //
+ // We found the SCB, increment the reference count and return
+ // success.
+ //
+
+ pScb = CONTAINING_RECORD( PrefixEntry, SCB, PrefixEntry );
+ pNpScb = pScb->pNpScb;
+
+ NwReferenceScb( pNpScb );
+
+ //
+ // Release the RCB.
+ //
+
+ NwReleaseRcb( &NwRcb );
+
+ DebugTrace(-1, Dbg, "NwFindScb -> %08lx\n", pScb );
+ *Scb = pScb;
+ return( TRUE );
+ }
+
+ //
+ // We do not have a connection to this server so create the new Scb if requested.
+ //
+
+ if ( BooleanFlagOn( IrpContext->Flags, IRP_FLAG_NOCONNECT ) ) {
+ NwReleaseRcb( &NwRcb );
+ *Scb = NULL;
+ return(FALSE);
+ }
+
+ try {
+
+ Status = NwAllocateAndInitScb( IrpContext,
+ UidServerName,
+ ServerName,
+ &pScb );
+
+ if ( !NT_SUCCESS( Status )) {
+ ExRaiseStatus( Status );
+ }
+
+ ASSERT( pScb != NULL );
+
+ pNpScb = pScb->pNpScb;
+
+ //
+ //*******************************************************************
+ //
+ // From this point on we must not fail to create the Scb because the
+ // another thread will be able to reference the Scb causing severe
+ // problems in the finaly clause or in the other thread.
+ //
+ //*******************************************************************
+ //
+
+ //
+ // Insert this SCB in the global list if SCBs.
+ // If it is the default (i.e. preferred) server, stick it at the
+ // front of the queue so that SelectConnection() will select it
+ // for bindery queries.
+ //
+
+ KeAcquireSpinLock(&ScbSpinLock, &OldIrql);
+
+ if ( pScb->PreferredServer ) {
+ InsertHeadList(&ScbQueue, &pNpScb->ScbLinks);
+ } else {
+ InsertTailList(&ScbQueue, &pNpScb->ScbLinks);
+ }
+
+ KeReleaseSpinLock(&ScbSpinLock, OldIrql);
+
+ //
+ // Insert the name of this server into the prefix table.
+ //
+
+ Success = RtlInsertUnicodePrefix(
+ &NwRcb.ServerNameTable,
+ &pScb->UidServerName,
+ &pScb->PrefixEntry );
+
+#ifdef NWDBG
+ if ( !Success ) {
+ DebugTrace( 0, DEBUG_TRACE_ALWAYS, "Entering duplicate SCB %wZ.\n", &pScb->UidServerName );
+ DbgBreakPoint();
+ }
+#endif
+
+ //
+ // The Scb is now in the prefix table. Any new requests for this
+ // connection can be added to the Scb->Requests queue while we
+ // attach to the server.
+ //
+
+ NwReleaseRcb( &NwRcb );
+ RcbHeld = FALSE;
+
+ //
+ // If we got an error we should have raised an exception.
+ //
+
+ ASSERT( NT_SUCCESS( Status ) );
+
+ } finally {
+
+ if ( !NT_SUCCESS( Status ) || AbnormalTermination() ) {
+ *Scb = NULL;
+ } else {
+ *Scb = pScb;
+ }
+
+ if (RcbHeld) {
+ NwReleaseRcb( &NwRcb );
+ }
+
+ }
+
+ return( FALSE );
+}
+
+NTSTATUS
+QueryServersAddress(
+ PIRP_CONTEXT pIrpContext,
+ PNONPAGED_SCB pNearestScb,
+ PUNICODE_STRING pServerName,
+ IPXaddress *pServerAddress
+ )
+{
+ NTSTATUS Status;
+ UNICODE_STRING NewServer;
+ USHORT CurrChar = 0;
+
+ PAGED_CODE();
+
+ //
+ // Unmunge the server name in case this is a
+ // supplemental credential connect.
+ //
+
+ UnmungeCredentialName( pServerName, &NewServer );
+
+ //
+ // Strip server name trailer, if it exists. If there
+ // was no trailer, the length will end up being exactly
+ // the same as when we started.
+ //
+
+ if (EnableMultipleConnects) {
+
+ while ( (CurrChar < (NewServer.Length / sizeof(WCHAR))) &&
+ NewServer.Buffer[CurrChar] != ((WCHAR)L'#') ) {
+ CurrChar++;
+ }
+ NewServer.Length = CurrChar * sizeof(WCHAR);
+
+ }
+
+ //
+ // Query the bindery of the nearest server looking for
+ // the network address of the target server.
+ //
+
+ DebugTrace( +0, Dbg, "Query servers address\n", 0);
+
+ Status = ExchangeWithWait (
+ pIrpContext,
+ SynchronousResponseCallback,
+ "SwUbp",
+ NCP_ADMIN_FUNCTION, NCP_QUERY_PROPERTY_VALUE,
+ OT_FILESERVER,
+ &NewServer,
+ 1, // Segment number
+ NET_ADDRESS_PROPERTY );
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ if ( NT_SUCCESS( Status ) ) {
+
+ //
+ // Save the new address.
+ //
+
+ Status = ParseResponse(
+ pIrpContext,
+ pIrpContext->rsp,
+ pIrpContext->ResponseLength,
+ "Nr",
+ pServerAddress, sizeof(TDI_ADDRESS_IPX) );
+ }
+
+ DebugTrace( +0, Dbg, " %X\n", Status);
+
+ //
+ // Map the server not found error to something sensible.
+ //
+
+ if (( Status == STATUS_NO_MORE_ENTRIES ) ||
+ ( Status == STATUS_VIRTUAL_CIRCUIT_CLOSED ) ||
+ ( Status == NwErrorToNtStatus(ERROR_UNEXP_NET_ERR))) {
+ Status = STATUS_BAD_NETWORK_PATH;
+ }
+
+ return( Status );
+}
+
+
+
+VOID
+TreeConnectScb(
+ IN PSCB Scb
+ )
+/*++
+
+Routine Description:
+
+ This routine increments the tree connect count for a SCB.
+
+Arguments:
+
+ Scb - A pointer to the SCB to connect to.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NwAcquireExclusiveRcb( &NwRcb, TRUE );
+ Scb->AttachCount++;
+ Scb->OpenFileCount++;
+ NwReferenceScb( Scb->pNpScb );
+ NwReleaseRcb( &NwRcb );
+}
+
+
+NTSTATUS
+TreeDisconnectScb(
+ IN PIRP_CONTEXT IrpContext,
+ IN PSCB Scb
+ )
+/*++
+
+Routine Description:
+
+ This routine decrements the tree connect count for a SCB.
+
+ *** This routine must be called with the RCB resource held.
+
+Arguments:
+
+ Scb - A pointer to the SCB to disconnect.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ NTSTATUS Status;
+
+ if ( Scb->AttachCount > 0 ) {
+
+ Scb->AttachCount--;
+ Scb->OpenFileCount--;
+ NwDereferenceScb( Scb->pNpScb );
+
+ Status = STATUS_SUCCESS;
+
+ if ( Scb->OpenFileCount == 0 ) {
+
+ //
+ // Logoff and disconnect from the server now.
+ // Hold on to the SCB lock.
+ // This prevents another thread from trying to access
+ // SCB will this thread is logging off.
+ //
+
+ NwLogoffAndDisconnect( IrpContext, Scb->pNpScb );
+ }
+ } else {
+ // FIXFIX just return success since we are disconnected?
+ Status = STATUS_INVALID_HANDLE;
+ }
+
+ NwDequeueIrpContext( IrpContext, FALSE );
+ return( Status );
+}
+
+
+VOID
+ReconnectScb(
+ IN PIRP_CONTEXT pIrpContext,
+ IN PSCB pScb
+ )
+/*++
+
+Routine Description:
+
+ This routine reconnects all the dir handles to a server
+ when reconnecting an SCB.
+
+
+Arguments:
+
+ pScb - A pointer to the SCB that has just been reconnected.
+
+Return Value:
+
+ None.
+
+--*/
+{
+ //
+ // If this is a reconnect, kill all old ICB and VCB handles
+ //
+
+ if ( pScb->VcbCount != 0 ) {
+
+ NwAcquireExclusiveRcb( &NwRcb, TRUE );
+
+ //
+ // Invalid all ICBs
+ //
+
+ NwInvalidateAllHandlesForScb( pScb );
+ NwReleaseRcb( &NwRcb );
+
+ //
+ // Acquire new VCB handles for all VCBs.
+ //
+
+ NwReopenVcbHandlesForScb( pIrpContext, pScb );
+ }
+}