diff options
Diffstat (limited to '')
-rw-r--r-- | private/nw/rdr/attach.c | 5169 |
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 ); + } +} |