diff options
Diffstat (limited to '')
-rw-r--r-- | private/nw/rdr/ndslogin.c | 3365 |
1 files changed, 3365 insertions, 0 deletions
diff --git a/private/nw/rdr/ndslogin.c b/private/nw/rdr/ndslogin.c new file mode 100644 index 000000000..953a4a4d2 --- /dev/null +++ b/private/nw/rdr/ndslogin.c @@ -0,0 +1,3365 @@ +/*++ + +Copyright (c) 1995 Microsoft Corporation + +Module Name: + + NdsLogin.c + +Abstract: + + This file implements the functionality required to + perform an NDS login. + +Author: + + Cory West [CoryWest] 23-Feb-1995 + +Revision History: + +--*/ + +#include "Procs.h" + +#define Dbg (DEBUG_TRACE_NDS) + +// +// Pageable. +// + +#pragma alloc_text( PAGE, NdsCanonUserName ) +#pragma alloc_text( PAGE, NdsCheckCredentials ) +#pragma alloc_text( PAGE, NdsCheckCredentialsEx ) +#pragma alloc_text( PAGE, NdsLookupCredentials ) +#pragma alloc_text( PAGE, NdsGetCredentials ) +#pragma alloc_text( PAGE, DoNdsLogon ) +#pragma alloc_text( PAGE, BeginLogin ) +#pragma alloc_text( PAGE, FinishLogin ) +#pragma alloc_text( PAGE, ChangeNdsPassword ) +#pragma alloc_text( PAGE, NdsServerAuthenticate ) +#pragma alloc_text( PAGE, BeginAuthenticate ) +#pragma alloc_text( PAGE, NdsLicenseConnection ) +#pragma alloc_text( PAGE, NdsUnlicenseConnection ) +#pragma alloc_text( PAGE, NdsGetBsafeKey ) + +// +// Note pageable: +// +// NdsTreeLogin (holds a spin lock) +// NdsLogoff (holds a spin lock) +// + +VOID +Shuffle( + UCHAR *achObjectId, + UCHAR *szUpperPassword, + int iPasswordLen, + UCHAR *achOutputBuffer +); + +NTSTATUS +NdsCanonUserName( + IN PNDS_SECURITY_CONTEXT pNdsContext, + IN PUNICODE_STRING puUserName, + IN OUT PUNICODE_STRING puCanonUserName +) +/*+++ + + Canonicalize the user name for the given tree and + current connection state. Canonicalization includes + handling the correct context and cleaning off all + the X500 prefixes. + + ALERT! The credential list must be held (shared or + exclusive) while this function is called. + +---*/ +{ + + NTSTATUS Status = STATUS_SUCCESS; + + USHORT CurrentTargetIndex; + int PrefixBytes; + + UNICODE_STRING UnstrippedName; + PWCHAR CanonBuffer; + + PAGED_CODE(); + + CanonBuffer = ALLOCATE_POOL( PagedPool, MAX_NDS_NAME_SIZE ); + if ( !CanonBuffer ) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // If the name starts with a dot, it's referenced from the root + // of the tree and we should not append the context. We should, + // however, strip off the leading dot so that name resolution + // will work. + // + + if ( puUserName->Buffer[0] == L'.' ) { + + UnstrippedName.Length = puUserName->Length - sizeof( WCHAR ); + UnstrippedName.MaximumLength = UnstrippedName.Length; + UnstrippedName.Buffer = &(puUserName->Buffer[1]); + + goto StripPrefixes; + } + + // + // If the name contains any dots, it's qualified and we + // should probably just use it as is. + // + + CurrentTargetIndex= 0; + + while ( CurrentTargetIndex< ( puUserName->Length / sizeof( WCHAR ) ) ) { + + if ( puUserName->Buffer[CurrentTargetIndex] == L'.' ) { + + UnstrippedName.Length = puUserName->Length; + UnstrippedName.MaximumLength = puUserName->Length; + UnstrippedName.Buffer = puUserName->Buffer; + + goto StripPrefixes; + } + + CurrentTargetIndex++; + } + + // + // If we have a context for this tree and the name isn't + // qualified, we should append the context. + // + + if ( pNdsContext->CurrentContext.Length ) { + + if ( ( puUserName->Length + + pNdsContext->CurrentContext.Length ) >= MAX_NDS_NAME_SIZE ) { + + DebugTrace( 0, Dbg, "NDS canon name too long.\n", 0 ); + Status = STATUS_INVALID_PARAMETER; + goto ExitWithCleanup; + } + + RtlCopyMemory( CanonBuffer, puUserName->Buffer, puUserName->Length ); + CanonBuffer[puUserName->Length / sizeof( WCHAR )] = L'.'; + + RtlCopyMemory( ((BYTE *)CanonBuffer) + puUserName->Length + sizeof( WCHAR ), + pNdsContext->CurrentContext.Buffer, + pNdsContext->CurrentContext.Length ); + + UnstrippedName.Length = puUserName->Length + + pNdsContext->CurrentContext.Length + + sizeof( WCHAR ); + UnstrippedName.MaximumLength = MAX_NDS_NAME_SIZE; + UnstrippedName.Buffer = CanonBuffer; + + goto StripPrefixes; + + } + + // + // It wasn't qualified, nor was there a context to append, so fail it. + // + + DebugTrace( 0, Dbg, "The name %wZ is not canonicalizable.\n", puUserName ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + +StripPrefixes: + + // + // All of these indexes are in BYTES, not WCHARS! + // + + CurrentTargetIndex = 0; + PrefixBytes = 0; + puCanonUserName->Length = 0; + + while ( ( CurrentTargetIndex < UnstrippedName.Length ) && + ( puCanonUserName->Length < puCanonUserName->MaximumLength ) ) { + + // + // Strip off the X.500 prefixes. + // + + if ( UnstrippedName.Buffer[CurrentTargetIndex / sizeof( WCHAR )] == L'=' ) { + + CurrentTargetIndex += sizeof( WCHAR ); + puCanonUserName->Length -= PrefixBytes; + PrefixBytes = 0; + + continue; + } + + puCanonUserName->Buffer[puCanonUserName->Length / sizeof( WCHAR )] = + UnstrippedName.Buffer[CurrentTargetIndex / sizeof( WCHAR )]; + + puCanonUserName->Length += sizeof( WCHAR ); + CurrentTargetIndex += sizeof( WCHAR ); + + if ( UnstrippedName.Buffer[CurrentTargetIndex / sizeof( WCHAR )] == L'.' ) { + PrefixBytes = 0; + PrefixBytes -= sizeof( WCHAR ); + } else { + PrefixBytes += sizeof( WCHAR ); + } + } + + DebugTrace( 0, Dbg, "Canonicalized name: %wZ\n", puCanonUserName ); + +ExitWithCleanup: + + FREE_POOL( CanonBuffer ); + return Status; +} + +NTSTATUS +NdsCheckCredentials( + IN PIRP_CONTEXT pIrpContext, + IN PUNICODE_STRING puUserName, + IN PUNICODE_STRING puPassword +) +/*++ + + Given a set of credentials and a username and password, + we need to determine if username and password match those + that were used to acquire the credentials. + +--*/ +{ + + NTSTATUS Status; + PLOGON pLogon; + PNONPAGED_SCB pNpScb; + PSCB pScb; + PNDS_SECURITY_CONTEXT pCredentials; + + PAGED_CODE(); + + // + // Grab the user's LOGON structure and credentials. + // + + pNpScb = pIrpContext->pNpScb; + pScb = pNpScb->pScb; + + NwAcquireExclusiveRcb( &NwRcb, TRUE ); + pLogon = FindUser( &pScb->UserUid, FALSE ); + NwReleaseRcb( &NwRcb ); + + if ( !pLogon ) { + DebugTrace( 0, Dbg, "Invalid client security context in NdsCheckCredentials.\n", 0 ); + return STATUS_ACCESS_DENIED; + } + + Status = NdsLookupCredentials( &pScb->NdsTreeName, + pLogon, + &pCredentials, + CREDENTIAL_READ, + FALSE ); + + if( NT_SUCCESS( Status ) ) { + + if ( pCredentials->CredentialLocked ) { + + Status = STATUS_DEVICE_BUSY; + + } else { + + Status = NdsCheckCredentialsEx( pIrpContext, + pLogon, + pCredentials, + puUserName, + puPassword ); + + } + + NwReleaseCredList( pLogon ); + } + + return Status; + +} + +NTSTATUS +NdsCheckCredentialsEx( + IN PIRP_CONTEXT pIrpContext, + IN PLOGON pLogon, + IN PNDS_SECURITY_CONTEXT pNdsContext, + IN PUNICODE_STRING puUserName, + IN PUNICODE_STRING puPassword +) +/*++ + + Given a set of credentials and a username and password, + we need to determine if username and password match those + that were used to acquire the credentials. + + ALERT! The credential list must be held (either shared or + exclusive) while this function is called. + +--*/ +{ + + NTSTATUS Status = STATUS_SUCCESS; + + UNICODE_STRING CredentialName; + + UNICODE_STRING CanonCredentialName, CanonUserName; + PWCHAR CredNameBuffer; + PWCHAR UserNameBuffer; + + UNICODE_STRING StoredPassword; + PWCHAR Stored; + + PAGED_CODE(); + + // + // If we haven't logged into to the tree, there is no security + // conflict. Otherwise, run the check. + // + + ASSERT ( pNdsContext->Credential != NULL ); + + CredNameBuffer = ALLOCATE_POOL( PagedPool, + ( 2 * MAX_NDS_NAME_SIZE ) + + ( MAX_PW_CHARS * sizeof( WCHAR ) ) ); + if ( !CredNameBuffer ) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + UserNameBuffer = (PWCHAR) (((BYTE *)CredNameBuffer) + MAX_NDS_NAME_SIZE ); + Stored = (PWCHAR) (((BYTE *)UserNameBuffer) + MAX_NDS_NAME_SIZE ); + + if ( puUserName && puUserName->Length ) { + + // + // Canon the incoming name and the credential name. + // + + CanonUserName.Length = 0; + CanonUserName.MaximumLength = MAX_NDS_NAME_SIZE; + CanonUserName.Buffer = UserNameBuffer; + + Status = NdsCanonUserName( pNdsContext, + puUserName, + &CanonUserName ); + + if ( !NT_SUCCESS( Status )) { + Status = STATUS_NETWORK_CREDENTIAL_CONFLICT; + goto ExitWithCleanup; + } + + CanonCredentialName.Length = 0; + CanonCredentialName.MaximumLength = MAX_NDS_NAME_SIZE; + CanonCredentialName.Buffer = CredNameBuffer; + + CredentialName.Length = (USHORT)pNdsContext->Credential->userNameLength - sizeof( WCHAR ); + CredentialName.MaximumLength = CredentialName.Length; + CredentialName.Buffer = (PWCHAR)( (PBYTE)(pNdsContext->Credential) + + sizeof( NDS_CREDENTIAL ) ); + + Status = NdsCanonUserName( pNdsContext, + &CredentialName, + &CanonCredentialName ); + + if ( !NT_SUCCESS( Status )) { + Status = STATUS_NETWORK_CREDENTIAL_CONFLICT; + goto ExitWithCleanup; + } + + // + // See if they match. + // + + if ( RtlCompareUnicodeString( &CanonUserName, &CanonCredentialName, TRUE )) { + DebugTrace( 0, Dbg, "NdsCheckCredentialsEx: user name conflict.\n", 0 ); + Status = STATUS_NETWORK_CREDENTIAL_CONFLICT; + goto ExitWithCleanup; + } + } + + if ( puPassword && puPassword->Length ) { + + // + // Now check the password. + // + + StoredPassword.Length = 0; + StoredPassword.MaximumLength = MAX_PW_CHARS * sizeof( WCHAR ); + StoredPassword.Buffer = Stored; + + RtlOemStringToUnicodeString( &StoredPassword, + &pNdsContext->Password, + FALSE ); + + if ( RtlCompareUnicodeString( puPassword, + &StoredPassword, + TRUE ) ) { + DebugTrace( 0, Dbg, "NdsCheckCredentialsEx: password conflict.\n", 0 ); + Status = STATUS_WRONG_PASSWORD; + goto ExitWithCleanup; + } + } + +ExitWithCleanup: + + FREE_POOL( CredNameBuffer ); + return Status; +} + +NTSTATUS +NdsLookupCredentials( + IN PUNICODE_STRING puTreeName, + IN PLOGON pLogon, + OUT PNDS_SECURITY_CONTEXT *ppCredentials, + DWORD dwDesiredAccess, + BOOLEAN fCreate +) +/*+++ + + Retrieve the nds credentials for the given tree from the + list of valid credentials for the specified user. + + puTreeName - The name of the tree that we want credentials for. If NULL + is specified, we return the credentials for the default tree. + pLogon - The logon structure for the user we want to access the tree. + ppCredentials - Where to put the pointed to the credentials. + dwDesiredAccess - CREDENTIAL_READ if we want read/only access, CREDENTIAL_WRITE + if we're going to change the credentials. + fCreate - If the credentials don't exist, should we create them? + + We return the credentials with the list held in the appropriate mode. The + caller is responsible for releasing the list when done with the credentials. + +---*/ +{ + + NTSTATUS Status; + + PLIST_ENTRY pFirst, pNext; + PNDS_SECURITY_CONTEXT pNdsContext; + + PAGED_CODE(); + + + // + // Check for existing credentials. + // + + if ( dwDesiredAccess == CREDENTIAL_READ ) { + NwAcquireSharedCredList( pLogon ); + } else { + NwAcquireExclusiveCredList( pLogon ); + } + + pFirst = &pLogon->NdsCredentialList; + pNext = pLogon->NdsCredentialList.Flink; + + while ( pNext && ( pFirst != pNext ) ) { + + pNdsContext = (PNDS_SECURITY_CONTEXT) + CONTAINING_RECORD( pNext, + NDS_SECURITY_CONTEXT, + Next ); + + ASSERT( pNdsContext->ntc == NW_NTC_NDS_CREDENTIAL ); + + if ( !puTreeName || + !RtlCompareUnicodeString( puTreeName, + &pNdsContext->NdsTreeName, + TRUE ) ) { + + // + // If the tree name is null, we'll return the first one + // on the list. Otherwise this will work as normal. + // + + *ppCredentials = pNdsContext; + return STATUS_SUCCESS; + } + + pNext = pNdsContext->Next.Flink; + + } + + // + // We didn't find the credential. Should we create it? + // + + NwReleaseCredList( pLogon ); + + if ( !fCreate || !puTreeName ) { + return STATUS_UNSUCCESSFUL; + } + + // + // Acquire exclusive since we're mucking with the list. + // + + NwAcquireExclusiveCredList( pLogon ); + + pNdsContext = ( PNDS_SECURITY_CONTEXT ) + ALLOCATE_POOL( PagedPool, sizeof( NDS_SECURITY_CONTEXT ) ); + + if ( !pNdsContext ) { + + DebugTrace( 0, Dbg, "Out of memory in NdsLookupCredentials.\n", 0 ); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto UnlockAndExit; + } + + // + // Initialize the structure. + // + + RtlZeroMemory( pNdsContext, sizeof( NDS_SECURITY_CONTEXT ) ); + pNdsContext->ntc = NW_NTC_NDS_CREDENTIAL; + pNdsContext->nts = sizeof( NDS_SECURITY_CONTEXT ); + + // + // Initialize the tree name. + // + + pNdsContext->NdsTreeName.MaximumLength = sizeof( pNdsContext->NdsTreeNameBuffer ); + pNdsContext->NdsTreeName.Buffer = pNdsContext->NdsTreeNameBuffer; + + RtlCopyUnicodeString( &pNdsContext->NdsTreeName, puTreeName ); + + // + // Initialize the context buffer. + // + + pNdsContext->CurrentContext.Length = 0; + pNdsContext->CurrentContext.MaximumLength = sizeof( pNdsContext->CurrentContextString ); + pNdsContext->CurrentContext.Buffer = pNdsContext->CurrentContextString; + + // + // Insert the context into the list. + // + + InsertHeadList( &pLogon->NdsCredentialList, &pNdsContext->Next ); + *ppCredentials = pNdsContext; + + // + // Release and re-acquire with the correct permissions. + // + + NwReleaseCredList( pLogon ); + + if ( dwDesiredAccess == CREDENTIAL_READ ) { + NwAcquireSharedCredList( pLogon ); + } else { + NwAcquireExclusiveCredList( pLogon ); + } + + // + // There's no chance that someone's going to come in during this + // small window and do a logout because there's no login data + // in the credentials. + // + + return STATUS_SUCCESS; + +UnlockAndExit: + + NwReleaseCredList( pLogon ); + return STATUS_UNSUCCESSFUL; + +} + +NTSTATUS +NdsGetCredentials( + IN PIRP_CONTEXT pIrpContext, + IN PLOGON pLogon, + IN PUNICODE_STRING puUserName, + IN PUNICODE_STRING puPassword +) +/*++ + + Do an NDS tree login and aquire a valid set of credentials. + +--*/ +{ + NTSTATUS Status; + + USHORT i; + UNICODE_STRING LoginName, LoginPassword; + PWCHAR NdsName; + PWCHAR NdsPassword; + + OEM_STRING OemPassword; + PBYTE OemPassBuffer; + PNDS_SECURITY_CONTEXT pNdsContext; + + PAGED_CODE(); + + // + // Prepare our login name by canonicalizing the supplied user + // name or using a default user name if appropriate. + // + + NdsName = ALLOCATE_POOL( PagedPool, MAX_NDS_NAME_SIZE + + MAX_PW_CHARS * sizeof( WCHAR ) + + MAX_PW_CHARS ); + + if ( !NdsName ) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + NdsPassword = (PWCHAR) (((BYTE *) NdsName) + MAX_NDS_NAME_SIZE ); + OemPassBuffer = ((BYTE *) NdsPassword ) + ( MAX_PW_CHARS * sizeof( WCHAR ) ); + + LoginName.Length = 0; + LoginName.MaximumLength = MAX_NDS_NAME_SIZE; + LoginName.Buffer = NdsName; + + Status = NdsLookupCredentials( &pIrpContext->pScb->NdsTreeName, + pLogon, + &pNdsContext, + CREDENTIAL_READ, + TRUE ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // If the credential list is locked, someone is logging + // out and we have to fail the request. + // + + if ( pNdsContext->CredentialLocked ) { + + Status = STATUS_DEVICE_BUSY; + NwReleaseCredList( pLogon ); + goto ExitWithCleanup; + } + + // + // Fix up the user name. + // ALERT! We are holding the credential list! + // + + if ( puUserName && puUserName->Buffer ) { + + Status = NdsCanonUserName( pNdsContext, + puUserName, + &LoginName ); + + if ( !NT_SUCCESS( Status )) { + Status = STATUS_NO_SUCH_USER; + } + + } else { + + // + // There's no name, so try the default name in the + // current context. + // + + if ( pNdsContext->CurrentContext.Length > 0 ) { + + // + // Make sure the lengths fit and all that. + // + + if ( ( pLogon->UserName.Length + + pNdsContext->CurrentContext.Length ) >= LoginName.MaximumLength ) { + + Status = STATUS_INVALID_PARAMETER; + goto NameResolved; + } + + RtlCopyMemory( LoginName.Buffer, pLogon->UserName.Buffer, pLogon->UserName.Length ); + LoginName.Buffer[pLogon->UserName.Length / sizeof( WCHAR )] = L'.'; + + RtlCopyMemory( ((BYTE *)LoginName.Buffer) + pLogon->UserName.Length + sizeof( WCHAR ), + pNdsContext->CurrentContext.Buffer, + pNdsContext->CurrentContext.Length ); + + LoginName.Length = pLogon->UserName.Length + + pNdsContext->CurrentContext.Length + + sizeof( WCHAR ); + + DebugTrace( 0, Dbg, "Using default name and context for login: %wZ\n", &LoginName ); + + } else { + Status = STATUS_NO_SUCH_USER; + } + + } + +NameResolved: + + NwReleaseCredList( pLogon ); + + // + // RELAX! The credential list is free. + // + + if ( !NT_SUCCESS( Status ) ) { + DebugTrace( 0, Dbg, "No name in NdsGetCredentials.\n", 0 ); + goto ExitWithCleanup; + } + + // + // If there's a password, use it. Otherwise, use the default password. + // + + if ( puPassword && puPassword->Buffer ) { + + LoginPassword.Length = puPassword->Length; + LoginPassword.MaximumLength = puPassword->MaximumLength; + LoginPassword.Buffer = puPassword->Buffer; + + } else { + + LoginPassword.Length = 0; + LoginPassword.MaximumLength = MAX_PW_CHARS * sizeof( WCHAR ); + LoginPassword.Buffer = NdsPassword; + + RtlCopyUnicodeString( &LoginPassword, + &pLogon->PassWord ); + } + + // + // Convert the password to upcase OEM and login. + // + + OemPassword.Length = 0; + OemPassword.MaximumLength = MAX_PW_CHARS; + OemPassword.Buffer = OemPassBuffer; + + Status = RtlUpcaseUnicodeStringToOemString( &OemPassword, + &LoginPassword, + FALSE ); + + if ( !NT_SUCCESS( Status )) { + Status = STATUS_WRONG_PASSWORD; + goto ExitWithCleanup; + } + + Status = NdsTreeLogin( pIrpContext, &LoginName, &OemPassword, NULL, pLogon ); + +ExitWithCleanup: + + FREE_POOL( NdsName ); + return Status; +} + +NTSTATUS +DoNdsLogon( + IN PIRP_CONTEXT pIrpContext, + IN PUNICODE_STRING UserName, + IN PUNICODE_STRING Password +) +/*+++ + +Description: + + This is the lead function for handling login and authentication to + Netware Directory Services. This function acquires credentials to + the appropriate tree for the server that the irp context points to, + logging us into that tree if necessary, and authenticates us to the + current server. + + BUGBUG: This routine gets called from reconnect attempts and from + normal requests. Since the allowable actions on each of these paths + are different, it might make sense to have two routines, each + more maintainable than this single routine. For now, watch out for + code in the RECONNECT_ATTEMPT cases. + +Arguments: + + pIrpContext - irp context; must refer to appropriate server + UserName - login username + Password - password + +--*/ +{ + + NTSTATUS Status; + PLOGON pLogon; + PNDS_SECURITY_CONTEXT pCredentials; + PSCB pScb; + UNICODE_STRING BinderyName; + UNICODE_STRING uUserName; + UNICODE_STRING NtGroup; + USHORT Length; + PSCB pOriginalServer = NULL; + DWORD UserOID; + + BOOL AtHeadOfQueue = FALSE; + BOOL HoldingCredentialResource = FALSE; + BOOL PasswordExpired = FALSE; + + PAGED_CODE(); + + // + // Get to the head of the queue if we need to. + // + + if ( BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_RECONNECT_ATTEMPT ) ) { + ASSERT( pIrpContext->pNpScb->Requests.Flink == &pIrpContext->NextRequest ); + } else { + NwAppendToQueueAndWait( pIrpContext ); + } + + AtHeadOfQueue = TRUE; + + // + // Grab the user's logon structure. + // + + pScb = pIrpContext->pScb; + + NwAcquireExclusiveRcb( &NwRcb, TRUE ); + pLogon = FindUser( &pScb->UserUid, FALSE ); + NwReleaseRcb( &NwRcb ); + + if ( !pLogon ) { + + DebugTrace( 0, Dbg, "Invalid client security context.\n", 0 ); + Status = STATUS_ACCESS_DENIED; + goto ExitWithCleanup; + } + + // + // Login and then re-get the tree credentials. + // + + Status = NdsLookupCredentials( &pScb->NdsTreeName, + pLogon, + &pCredentials, + CREDENTIAL_READ, + FALSE ); + + if ( !NT_SUCCESS( Status ) ) { + HoldingCredentialResource = FALSE; + goto LOGIN; + } + + HoldingCredentialResource = TRUE; + + // + // Are we logged in? We can't hold the + // credential list while logging in!! + // + + if ( !pCredentials->Credential ) { + + HoldingCredentialResource = FALSE; + NwReleaseCredList( pLogon ); + goto LOGIN; + } + + // + // If this credential is locked, we fail! + // + + if ( pCredentials->CredentialLocked ) { + Status = STATUS_DEVICE_BUSY; + goto ExitWithCleanup; + } + + Status = NdsCheckCredentialsEx( pIrpContext, + pLogon, + pCredentials, + UserName, + Password ); + + if( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + goto AUTHENTICATE; + +LOGIN: + + ASSERT( HoldingCredentialResource == FALSE ); + + // + // If this is a reconnect attempt and we don't have credentials + // already, we have to give up. We can't acquire credentials + // during a reconnect and retry because it could cause a deadlock. + // + + if ( BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_RECONNECT_ATTEMPT ) ) { + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + Status = NdsGetCredentials( pIrpContext, + pLogon, + UserName, + Password ); + + if ( !NT_SUCCESS( Status )) { + goto ExitWithCleanup; + } + + if ( Status == NWRDR_PASSWORD_HAS_EXPIRED ) { + PasswordExpired = TRUE; + } + + Status = NdsLookupCredentials( &pScb->NdsTreeName, + pLogon, + &pCredentials, + CREDENTIAL_READ, + FALSE ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // If this credential is locked, someone is + // already logging out and so we fail this. + // + + if ( pCredentials->CredentialLocked ) { + Status = STATUS_DEVICE_BUSY; + NwReleaseCredList( pLogon ); + goto ExitWithCleanup; + } + + HoldingCredentialResource = TRUE; + +AUTHENTICATE: + + ASSERT( HoldingCredentialResource == TRUE ); + ASSERT( AtHeadOfQueue == TRUE ); + + // + // NdsServerAuthenticate will not take us off the + // head of the queue since this is not allowed. + // + + Status = NdsServerAuthenticate( pIrpContext, pCredentials ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + NwReleaseCredList( pLogon ); + HoldingCredentialResource = FALSE; + + // + // If this is a gateway request and is not a reconnect attempt, we + // need to check the group membership. + // + + if ( pIrpContext->Specific.Create.UserUid.QuadPart == DefaultLuid.QuadPart) { + + if ( !BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_RECONNECT_ATTEMPT ) ) { + + NwDequeueIrpContext( pIrpContext, FALSE ); + AtHeadOfQueue = FALSE; + + // + // Resolve the name, allowing a server jump if necessary. + // + + pOriginalServer = pIrpContext->pScb; + NwReferenceScb( pOriginalServer->pNpScb ); + + uUserName.MaximumLength = pCredentials->Credential->userNameLength; + uUserName.Length = uUserName.MaximumLength; + uUserName.Buffer = ( WCHAR * ) ( ((BYTE *)pCredentials->Credential) + + sizeof( NDS_CREDENTIAL ) + + pCredentials->Credential->optDataSize ); + + Status = NdsResolveNameKm( pIrpContext, + &uUserName, + &UserOID, + TRUE, + DEFAULT_RESOLVE_FLAGS ); + + if ( NT_SUCCESS(Status) ) { + + RtlInitUnicodeString( &NtGroup, NT_GATEWAY_GROUP ); + Status = NdsCheckGroupMembership( pIrpContext, UserOID, &NtGroup ); + + if ( !NT_SUCCESS( Status ) ) { + DebugTrace( 0, Dbg, "Gateway connection to NDS server not allowed.\n", 0 ); + Status = STATUS_ACCESS_DENIED; + } + } + + // + // Take us off the head of the queue in case were were left there. + // + + NwDequeueIrpContext( pIrpContext, FALSE ); + + // + // Restore us to the server that we authenticated to so that + // the irp context refers to the authenticated server. + // + + if ( pOriginalServer != NULL ) { + + NwDereferenceScb( pIrpContext->pNpScb ); + + if ( pIrpContext->pScb != pOriginalServer ) { + pIrpContext->pScb = pOriginalServer; + pIrpContext->pNpScb = pOriginalServer->pNpScb; + } + } + + } + } + +ExitWithCleanup: + + if ( HoldingCredentialResource ) { + NwReleaseCredList( pLogon ); + } + + if ( AtHeadOfQueue ) { + + // + // If we failed and this was a reconnect attempt, don't dequeue the + // irp context or we may deadlock when we try to do the bindery logon. + // See ReconnectRetry() for more information on this restriction. + // + + if ( !BooleanFlagOn( pIrpContext->Flags, IRP_FLAG_RECONNECT_ATTEMPT ) ) { + NwDequeueIrpContext( pIrpContext, FALSE ); + } + + } + + if ( ( NT_SUCCESS( Status ) ) && + ( PasswordExpired ) ) { + Status = NWRDR_PASSWORD_HAS_EXPIRED; + } + + DebugTrace( 0, Dbg, "DoNdsLogin: Status = %08lx\n", Status ); + return Status; +} + +NTSTATUS +NdsTreeLogin( + IN PIRP_CONTEXT pIrpContext, + IN PUNICODE_STRING puUser, + IN POEM_STRING pOemPassword, + IN POEM_STRING pOemNewPassword, + IN PLOGON pUserLogon +) +/*++ + +Routine Description: + + Login the specified user to the NDS tree at the server referred + to by the given IrpContext using the supplied password. + +Arguments: + + pIrpContext - The irp context for this server connection. + puUser - The user login name. + pOemPassword - The upper-case, plaintext password. + pOemNewPassword - The new password for a change pass request. + pUserLogon - The LOGON security structure for this user, + which may be NULL for a change password + request. + +Side Effects: + + If successful, the user's credentials, signature, and + public key are saved in the nds context for this NDS tree + in the credential list in the LOGON structure. + +Notes: + + This function may have to jump around a few servers to + get all the info needed for login. If restores the irp + context to the original server so that when we authenticate, + we authenticate to the correct server (as requested by the + user). + +--*/ +{ + NTSTATUS Status; // status of the operation + int CryptStatus; // crypt status + + DWORD dwChallenge; // four byte server challenge + PUNICODE_STRING puServerName; // server's distinguished name + + DWORD dwUserOID, // user oid on the current server + dwSrcUserOID, // user oid on the originating server + dwServerOID; // server oid + + BYTE *pbServerPublicNdsKey, // server's public key in NDS format + *pbServerPublicBsafeKey; // server's public BSAFE key + + int cbServerPublicNdsKeyLen, // length of server public NDS key + cbServerPublicBsafeKeyLen; // length of server pubilc BSAFE key + + BYTE *pbUserPrivateNdsKey, // user's private key in NDS format + *pbUserPrivateBsafeKey; // user's private BSAFE key + + int cbUserPrivateNdsKeyLen; // length of user private NDS key + WORD cbUserPrivateBsafeKeyLen; // length of user private BSAFE key + + BYTE pbNw3PasswdHash[16]; // nw3 passwd hash + BYTE pbNewPasswdHash[16]; // new passwd hash for change pass + BYTE pbPasswdHashRC2Key[8]; // RC2 secret key generated from hash + + BYTE pbEncryptedChallenge[16]; // RC2 encrypted server challenge + int cbEncryptedChallengeLen; // length of the encrypted challenge + + PNDS_SECURITY_CONTEXT psNdsSecurityContext; // user's nds context + BYTE *pbSignData; // user's signature data + + UNICODE_STRING uUserDN; // users fully distinguished name + PWCHAR UserDnBuffer; + + DWORD dwValidityStart, dwValidityEnd; + BOOLEAN AtHeadOfQueue = FALSE; + BOOLEAN HoldingCredResource = FALSE; + BOOLEAN PasswordExpired = FALSE; + + UNICODE_STRING PlainServerName; + USHORT UidLen; + KIRQL OldIrql; + PSCB pLoginServer = NULL; + PSCB pOriginalServer = NULL; + DWORD dwLoginFlags = 0; + + DebugTrace( 0, Dbg, "Enter NdsTreeLogin...\n", 0 ); + + ASSERT( puUser ); + ASSERT( pOemPassword ); + + // + // Allocate space for the server's public key and the user's private key. + // + + cbServerPublicNdsKeyLen = MAX_PUBLIC_KEY_LEN + MAX_ENC_PRIV_KEY_LEN + MAX_NDS_NAME_SIZE; + + pbServerPublicNdsKey = ALLOCATE_POOL( PagedPool, cbServerPublicNdsKeyLen ); + + if ( !pbServerPublicNdsKey ) { + + DebugTrace( 0, Dbg, "Out of memory in NdsTreeLogin...\n", 0 ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // First, jump to a server where we can get this user object. + // Don't forget the server to which we were originally pointed. + // + + pOriginalServer = pIrpContext->pScb; + NwReferenceScb( pOriginalServer->pNpScb ); + + Status = NdsResolveNameKm( pIrpContext, + puUser, + &dwUserOID, + TRUE, + DEFAULT_RESOLVE_FLAGS ); + + if ( !NT_SUCCESS( Status ) ) { + if ( Status == STATUS_BAD_NETWORK_PATH ) { + Status = STATUS_NO_SUCH_USER; + } + goto ExitWithCleanup; + } + + // + // Now get the user name from the object info. + // + + UserDnBuffer = (PWCHAR) ( pbServerPublicNdsKey + + MAX_PUBLIC_KEY_LEN + + MAX_ENC_PRIV_KEY_LEN ); + + uUserDN.Length = 0; + uUserDN.MaximumLength = MAX_NDS_NAME_SIZE; + uUserDN.Buffer = UserDnBuffer; + + Status = NdsGetUserName( pIrpContext, + dwUserOID, + &uUserDN ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // Get the name of the server we are currently on. We borrow a + // little space from our key buffer and overwrite it later. + // + + puServerName = ( PUNICODE_STRING ) pbServerPublicNdsKey; + puServerName->Buffer = (PWCHAR) pbServerPublicNdsKey + sizeof( UNICODE_STRING ); + puServerName->MaximumLength = cbServerPublicNdsKeyLen - sizeof( UNICODE_STRING ); + + Status = NdsGetServerName( pIrpContext, + puServerName ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // If the public key for this server is on a partition that's + // on another server, we'll have to jump over there to get the + // public key and then return. The key and user object are + // only any good on this server! DO NOT CHANGE THE ORDER OF + // THIS OR IT WILL BREAK! + // + + pLoginServer = pIrpContext->pScb; + NwReferenceScb( pLoginServer->pNpScb ); + + Status = NdsResolveNameKm( pIrpContext, + puServerName, + &dwServerOID, + TRUE, + DEFAULT_RESOLVE_FLAGS ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // Get the server's public key and its length. + // + + Status = NdsReadPublicKey( pIrpContext, + dwServerOID, + pbServerPublicNdsKey, + &cbServerPublicNdsKeyLen ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // Return us to the login server. + // + + if ( pLoginServer != pIrpContext->pScb ) { + + NwDequeueIrpContext( pIrpContext, FALSE ); + NwDereferenceScb( pIrpContext->pNpScb ); + pIrpContext->pScb = pLoginServer; + pIrpContext->pNpScb = pLoginServer->pNpScb; + + } else { + + NwDereferenceScb( pLoginServer->pNpScb ); + } + + pLoginServer = NULL; + + // + // Locate the BSAFE key in the NDS key. + // + + cbServerPublicBsafeKeyLen = NdsGetBsafeKey( pbServerPublicNdsKey, + cbServerPublicNdsKeyLen, + &pbServerPublicBsafeKey ); + + if ( !cbServerPublicBsafeKeyLen ) { + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + // + // Send the begin login packet. This returns to us the + // 4 byte challenge and the object id of the user's account + // on the server on which it was created. It may be the + // same as the object id that we provided if the account + // was created on this server. + // + + Status = BeginLogin( pIrpContext, + dwUserOID, + &dwSrcUserOID, + &dwChallenge ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // Compute the 16 byte NW3 hash and generate the + // 8 byte secret key from it. The 8 byte secret + // key consists of a MAC checksum of the NW3 hash. + // + + Shuffle( (UCHAR *)&dwSrcUserOID, + pOemPassword->Buffer, + pOemPassword->Length, + pbNw3PasswdHash ); + + GenKey8( pbNw3PasswdHash, + sizeof( pbNw3PasswdHash ), + pbPasswdHashRC2Key ); + + // + // RC2 Encrypt the 4 byte challenge using the secret + // key generated from the password. + // + + CryptStatus = CBCEncrypt( pbPasswdHashRC2Key, + NULL, + (BYTE *)&dwChallenge, + 4, + pbEncryptedChallenge, + &cbEncryptedChallengeLen, + BSAFE_CHECKSUM_LEN ); + + if ( CryptStatus ) { + + DebugTrace( 0, Dbg, "CBC encryption failed.\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + pbUserPrivateNdsKey = pbServerPublicNdsKey + MAX_PUBLIC_KEY_LEN; + cbUserPrivateNdsKeyLen = MAX_ENC_PRIV_KEY_LEN; + + // + // Make the finish login packet. If successful, this routine + // returns the encrypted user private key and the valid duration + // of the user's credentials. + // + + if ( pOemNewPassword ) { + dwLoginFlags = 1; + } + + Status = FinishLogin( pIrpContext, + dwUserOID, + dwLoginFlags, + pbEncryptedChallenge, + pbServerPublicBsafeKey, + cbServerPublicBsafeKeyLen, + pbUserPrivateNdsKey, + &cbUserPrivateNdsKeyLen, + &dwValidityStart, + &dwValidityEnd ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + if ( !pOemNewPassword ) { + + // + // If the password is expired, report it to the user. + // + + if ( Status == NWRDR_PASSWORD_HAS_EXPIRED ) { + PasswordExpired = TRUE; + } + + // + // Allocate the credential and set up space for the private key. + // + + NwAppendToQueueAndWait( pIrpContext ); + AtHeadOfQueue = TRUE; + + Status = NdsLookupCredentials( &pIrpContext->pScb->NdsTreeName, + pUserLogon, + &psNdsSecurityContext, + CREDENTIAL_WRITE, + TRUE ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // ALERT! We are holding the credential list. + // + + HoldingCredResource = TRUE; + + psNdsSecurityContext->Credential = ALLOCATE_POOL( PagedPool, + sizeof( NDS_CREDENTIAL ) + + uUserDN.Length ); + + if ( !psNdsSecurityContext->Credential ) { + + DebugTrace( 0, Dbg, "Out of memory in NdsTreeLogin (for credential)...\n", 0 ); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto ExitWithCleanup; + + } + + *( (UNALIGNED DWORD *) &( psNdsSecurityContext->Credential->validityBegin ) ) = dwValidityStart; + *( (UNALIGNED DWORD *) &( psNdsSecurityContext->Credential->validityEnd ) ) = dwValidityEnd; + + DebugTrace( 0, Dbg, "Credential validity start: 0x%08lx\n", dwValidityStart ); + DebugTrace( 0, Dbg, "Credential validity end: 0x%08lx\n", dwValidityEnd ); + + // + // RC2 Decrypt the response to extract the BSAFE private + // key data in place. + // + + CryptStatus = CBCDecrypt( pbPasswdHashRC2Key, + NULL, + pbUserPrivateNdsKey, + cbUserPrivateNdsKeyLen, + pbUserPrivateNdsKey, + &cbUserPrivateNdsKeyLen, + BSAFE_CHECKSUM_LEN ); + + if ( CryptStatus ) { + + DebugTrace( 0, Dbg, "CBC decryption failed.\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + // + // Skip over the header. + // + + pbUserPrivateBsafeKey = ( pbUserPrivateNdsKey + sizeof( TAG_DATA_HEADER ) ); + cbUserPrivateBsafeKeyLen = *( ( WORD * ) pbUserPrivateBsafeKey ); + pbUserPrivateBsafeKey += sizeof( WORD ); + + // + // Create the credential. + // + + psNdsSecurityContext->Credential->tdh.version = 1; + psNdsSecurityContext->Credential->tdh.tag = TAG_CREDENTIAL; + + GenRandomBytes( ( BYTE * ) &(psNdsSecurityContext->Credential->random), + sizeof( psNdsSecurityContext->Credential->random ) ); + + psNdsSecurityContext->Credential->optDataSize = 0; + psNdsSecurityContext->Credential->userNameLength = uUserDN.Length; + + RtlCopyMemory( ( (BYTE *)psNdsSecurityContext->Credential) + sizeof( NDS_CREDENTIAL ), + UserDnBuffer, + uUserDN.Length ); + + // + // Generate and save the signature. + // + + psNdsSecurityContext->Signature = ALLOCATE_POOL( PagedPool, MAX_SIGNATURE_LEN ); + + if ( !psNdsSecurityContext->Signature ) { + + DebugTrace( 0, Dbg, "Out of memory in NdsTreeLogin (for signature)...\n", 0 ); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto ExitWithCleanup; + + } + + pbSignData = ( ( ( BYTE * ) psNdsSecurityContext->Signature ) + + sizeof( NDS_SIGNATURE ) ); + + RtlZeroMemory( pbSignData, MAX_RSA_BYTES ); + + psNdsSecurityContext->Signature->tdh.version = 1; + psNdsSecurityContext->Signature->tdh.tag = TAG_SIGNATURE; + + // + // Create the hash for the signature from the credential. + // + + MD2( (BYTE *) psNdsSecurityContext->Credential, + sizeof( NDS_CREDENTIAL ) + ( uUserDN.Length ), + pbSignData ); + + // + // Compute the 'signature' by RSA-encrypting the + // 16-byte signature hash with the private key. + // + + psNdsSecurityContext->Signature->signDataLength = RSAPrivate( pbUserPrivateBsafeKey, + cbUserPrivateBsafeKeyLen, + pbSignData, + 16, + pbSignData ); + + if ( !psNdsSecurityContext->Signature->signDataLength ) { + + DebugTrace( 0, Dbg, "RSA private encryption for signature failed.\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + // + // Round up the signature length, cause that's how VLM stores it. + // + + psNdsSecurityContext->Signature->signDataLength = + ROUNDUP4( psNdsSecurityContext->Signature->signDataLength ); + + DebugTrace( 0, Dbg, "Signature data length: %d\n", + psNdsSecurityContext->Signature->signDataLength ); + + // + // Get the user's public key for storage in the nds context. + // + + psNdsSecurityContext->PublicNdsKey = ALLOCATE_POOL( PagedPool, MAX_PUBLIC_KEY_LEN ); + + if ( !psNdsSecurityContext->PublicNdsKey ) { + + DebugTrace( 0, Dbg, "Out of memory in NdsTreeLogin (for public key)...\n", 0 ); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto ExitWithCleanup; + + } + + psNdsSecurityContext->PublicKeyLen = MAX_PUBLIC_KEY_LEN; + + ASSERT( AtHeadOfQueue ); + ASSERT( HoldingCredResource ); + + Status = NdsReadPublicKey( pIrpContext, + dwUserOID, + psNdsSecurityContext->PublicNdsKey, + &(psNdsSecurityContext->PublicKeyLen) ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // Store away the password we used to connect. + // + + psNdsSecurityContext->Password.Buffer = ALLOCATE_POOL( PagedPool, pOemPassword->Length ); + + if ( !psNdsSecurityContext->Password.Buffer ) { + + DebugTrace( 0, Dbg, "Out of memory in NdsTreeLogin (for password)\n", 0 ); + Status = STATUS_INSUFFICIENT_RESOURCES; + goto ExitWithCleanup; + } + + psNdsSecurityContext->Password.Length = pOemPassword->Length; + psNdsSecurityContext->Password.MaximumLength = pOemPassword->Length; + RtlCopyMemory( psNdsSecurityContext->Password.Buffer, + pOemPassword->Buffer, + pOemPassword->Length ); + + // + // We are logged in to the NDS tree. Should we zero the private + // key, or is NT's protection sufficient? + // + + NwReleaseCredList( pUserLogon ); + + // + // Try to elect this server as the preferred server if necessary. + // + + NwAcquireExclusiveRcb( &NwRcb, TRUE ); + + if ( ( pUserLogon->ServerName.Length == 0 ) && + ( !pIrpContext->Specific.Create.fExCredentialCreate ) ) { + + // + // Strip off the unicode uid from the server name. + // + + PlainServerName.Length = pIrpContext->pScb->UidServerName.Length; + PlainServerName.Buffer = pIrpContext->pScb->UidServerName.Buffer; + + UidLen = 0; + + while ( UidLen < ( PlainServerName.Length / sizeof( WCHAR ) ) ) { + + if ( PlainServerName.Buffer[UidLen++] == L'\\' ) { + break; + } + } + + PlainServerName.Buffer += UidLen; + PlainServerName.Length -= ( UidLen * sizeof( WCHAR ) ); + PlainServerName.MaximumLength = PlainServerName.Length; + + if ( PlainServerName.Length ) { + + Status = SetUnicodeString( &(pUserLogon->ServerName), + PlainServerName.Length, + PlainServerName.Buffer ); + + if ( NT_SUCCESS( Status ) ) { + + DebugTrace( 0, Dbg, "Electing preferred server: %wZ\n", &PlainServerName ); + + // + // Increase the Scb ref count, set the preferred server flag, + // and move the scb to the head of the SCB list. + // + + NwReferenceScb( pIrpContext->pScb->pNpScb ); + pIrpContext->pScb->PreferredServer = TRUE; + + KeAcquireSpinLock( &ScbSpinLock, &OldIrql ); + + RemoveEntryList( &(pIrpContext->pScb->pNpScb->ScbLinks) ); + InsertHeadList( &ScbQueue, &(pIrpContext->pScb->pNpScb->ScbLinks) ); + + KeReleaseSpinLock(&ScbSpinLock, OldIrql); + + } + } + } + + } else { + + // + // This isn't a login, but a change password request. + // + // First we have to RC2 Decrypt the response to extract + // the BSAFE private key data in place (just like for a + // login). + // + + CryptStatus = CBCDecrypt( pbPasswdHashRC2Key, + NULL, + pbUserPrivateNdsKey, + cbUserPrivateNdsKeyLen, + pbUserPrivateNdsKey, + &cbUserPrivateNdsKeyLen, + BSAFE_CHECKSUM_LEN ); + + if ( CryptStatus ) { + + DebugTrace( 0, Dbg, "CBC decryption failed.\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + // + // Now, compute the hash of the new password. + // + + Shuffle( (UCHAR *)&dwSrcUserOID, + pOemNewPassword->Buffer, + pOemNewPassword->Length, + pbNewPasswdHash ); + + // + // And finally, make the request. + // + + Status = ChangeNdsPassword( pIrpContext, + dwUserOID, + dwChallenge, + pbNw3PasswdHash, + pbNewPasswdHash, + ( PNDS_PRIVATE_KEY ) pbUserPrivateNdsKey, + pbServerPublicBsafeKey, + cbServerPublicBsafeKeyLen ); + + if ( !NT_SUCCESS( Status ) ) { + DebugTrace( 0, Dbg, "Change NDS password failed!\n", 0 ); + goto ExitWithCleanup; + } + + } + + // + // Return us to our original server if we've jumped around. + // + + NwDequeueIrpContext( pIrpContext, FALSE ); + + if ( pIrpContext->pScb != pOriginalServer ) { + + NwDereferenceScb( pIrpContext->pNpScb ); + pIrpContext->pScb = pOriginalServer; + pIrpContext->pNpScb = pOriginalServer->pNpScb; + + } else { + + NwDereferenceScb( pOriginalServer->pNpScb ); + } + + pOriginalServer = NULL; + + if ( !pOemNewPassword ) { + NwReleaseRcb( &NwRcb ); + } + + FREE_POOL( pbServerPublicNdsKey ); + + if ( PasswordExpired ) { + Status = NWRDR_PASSWORD_HAS_EXPIRED; + } else { + Status = STATUS_SUCCESS; + } + + return Status; + +ExitWithCleanup: + + DebugTrace( 0, Dbg, "NdsTreeLogin seems to have failed... cleaning up.\n", 0 ); + + FREE_POOL( pbServerPublicNdsKey ); + + if ( pLoginServer ) { + NwDereferenceScb( pLoginServer->pNpScb ); + } + + // + // If we failed after jumping servers, we have to restore + // the irp context to the original server. + // + + NwDequeueIrpContext( pIrpContext, FALSE ); + + if ( pOriginalServer ) { + + if ( pIrpContext->pScb != pOriginalServer ) { + + NwDereferenceScb( pIrpContext->pNpScb ); + pIrpContext->pScb = pOriginalServer; + pIrpContext->pNpScb = pOriginalServer->pNpScb; + + } else { + + NwDereferenceScb( pOriginalServer->pNpScb ); + } + + } + + if ( HoldingCredResource ) { + + if ( psNdsSecurityContext->Credential ) { + FREE_POOL( psNdsSecurityContext->Credential ); + psNdsSecurityContext->Credential = NULL; + } + + if ( psNdsSecurityContext->Signature ) { + FREE_POOL( psNdsSecurityContext->Signature ); + psNdsSecurityContext->Signature = NULL; + } + + if ( psNdsSecurityContext->PublicNdsKey ) { + FREE_POOL( psNdsSecurityContext->PublicNdsKey ); + psNdsSecurityContext->PublicNdsKey = NULL; + psNdsSecurityContext->PublicKeyLen = 0; + } + + NwReleaseCredList( pUserLogon ); + } + + return Status; + +} + +NTSTATUS +BeginLogin( + IN PIRP_CONTEXT pIrpContext, + IN DWORD userId, + OUT DWORD *loginId, + OUT DWORD *challenge +) +/*++ + +Routine Desription: + + Begin the NDS login process. The loginId returned is objectId of the user + on the server which created the account (may not be the current server). + +Arguments: + + pIrpContext - The IRP context for this connection. + userId - The user's NDS object Id. + loginId - The objectId used to encrypt password. + challenge - The 4 byte random challenge. + +Return value: + + NTSTATUS - The result of the operation. + +--*/ +{ + + NTSTATUS Status; + LOCKED_BUFFER NdsRequest; + + PAGED_CODE(); + + DebugTrace( 0, Dbg, "Enter BeginLogin...\n", 0 ); + + Status = NdsAllocateLockedBuffer( &NdsRequest, NDS_BUFFER_SIZE ); + + if ( !NT_SUCCESS( Status ) ) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // Announce myself. + // + + Status = FragExWithWait( pIrpContext, + NDSV_BEGIN_LOGIN, + &NdsRequest, + "DD", + 0, + userId ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + Status = NdsCompletionCodetoNtStatus( &NdsRequest ); + + if ( !NT_SUCCESS( Status ) ) { + + if ( Status == STATUS_BAD_NETWORK_PATH ) { + Status = STATUS_NO_SUCH_USER; + } + + goto ExitWithCleanup; + } + + // + // Get the object id and the challenge string. + // + + Status = ParseResponse( NULL, + NdsRequest.pRecvBufferVa, + NdsRequest.dwBytesWritten, + "G_DD", + sizeof( DWORD ), + loginId, + challenge ); + + if ( NT_SUCCESS( Status ) ) { + DebugTrace( 0, Dbg, "Login 4 byte challenge: 0x%08lx\n", *challenge ); + } else { + DebugTrace( 0, Dbg, "Begin login failed...\n", 0 ); + } + +ExitWithCleanup: + + NdsFreeLockedBuffer( &NdsRequest ); + return Status; + +} + +NTSTATUS +FinishLogin( + IN PIRP_CONTEXT pIrpContext, + IN DWORD dwUserOID, + IN DWORD dwLoginFlags, + IN BYTE pbEncryptedChallenge[16], + IN BYTE *pbServerPublicBsafeKey, + IN int cbServerPublicBsafeKeyLen, + OUT BYTE *pbUserEncPrivateNdsKey, + OUT int *pcbUserEncPrivateNdsKeyLen, + OUT DWORD *pdwCredentialStartTime, + OUT DWORD *pdwCredentialEndTime +) +/*++ + +Routine Description: + + Constructs and sends the Finish Login request to the server. + +Arguments: + + pIrpContext - (IN) IRP context for this request + dwUserOID - (IN) user's NDS object Id + pbEncryptedChallenge - (IN) RC2 encrypted challenge + pbServerPublicBsafeKey - (IN) server public bsafe key + cbServerPublicBsafeKeyLen - (IN) length of server public key + + pbUserEncPrivateNdsKey - (OUT) user's encrypted private nds key + pcbUserEncPrivateNdsKeyLen - (OUT) length of pbUserEncPrivateNdsKey + pdwCredentialStartTime - (OUT) validity start time for credentials + pdwCredentialEndTime - (OUT) validity end time for credentials + +--*/ +{ + NTSTATUS Status; + + const int cbEncryptedChallengeLen = 16; + + int LOG_DATA_POOL_SIZE, // pool sizes for our allocation call + PACKET_POOL_SIZE, + RESP_POOL_SIZE; + + BYTE *pbRandomBytes; // random bytes used in crypto routines + BYTE RandRC2SecretKey[RC2_KEY_LEN]; // random RC2 key generated from above + BYTE pbEncryptedChallengeKey[RC2_KEY_LEN]; // RC2 key that will decode the response + + NDS_RAND_BYTE_BLOCK *psRandByteBlock; + + ENC_BLOCK_HDR *pbEncRandSeedHead; // header for encrypted random RC2 key seed + BYTE *pbEncRandSeed; // encrypted random seed + int cbPackedRandSeedLen; // length of the packed rand seed bytes + + ENC_BLOCK_HDR *pbEncChallengeHead; // header for encrypted challenge + + ENC_BLOCK_HDR *pbEncLogDataHead; // header for encrypted login data + BYTE *pbEncLogData; // encrypted login data + int cbEncLogDataLen; // length of the encrypted login data + + ENC_BLOCK_HDR *pbEncServerRespHead; // header for encrypted response + BYTE *pbEncServerResp; // encrypted response + + int CryptStatus, // crypt call status + CryptLen, // length of encrypted data + RequestPacketLen, // length of the request packet data + cbServerRespLen; // server response length after decryption + + BYTE *pbServerResponse; // response from the server + DWORD cbEncServerRespLen; // server response length before decryption + + DWORD EncKeyLen; // length of the encrypted private key + ENC_BLOCK_HDR *pbPrivKeyHead; // encryption header of the private key + + LOCKED_BUFFER NdsRequest; // fragex locked buffer + BOOL PasswordExpired = FALSE; + + PAGED_CODE(); + + DebugTrace( 0, Dbg, "Enter FinishLogin...\n", 0 ); + + // + // Allocate working space. The login data pool starts at + // pbRandomBytes. The packet data starts at pbEncRandSeedHead. + // The server response pool starts at pbServerResponse. + // + + // + // BUGBUG: The alignment of these structures may possibly be wrong on + // quad aligned machines; check out a hardware independent fix. + // + + LOG_DATA_POOL_SIZE = RAND_KEY_DATA_LEN + // 28 bytes for random seed + sizeof ( NDS_RAND_BYTE_BLOCK ) + // login data random header + sizeof ( ENC_BLOCK_HDR ) + // header for encrypted challenge + cbEncryptedChallengeLen + // data for encrypted challenge + 8; // padding + LOG_DATA_POOL_SIZE = ROUNDUP4( LOG_DATA_POOL_SIZE ); + + PACKET_POOL_SIZE = 2048; // packet buffer size + RESP_POOL_SIZE = 2048; // packet buffer size + + pbRandomBytes = ALLOCATE_POOL( PagedPool, + LOG_DATA_POOL_SIZE + + PACKET_POOL_SIZE + + RESP_POOL_SIZE ); + + if ( !pbRandomBytes ) { + + DebugTrace( 0, Dbg, "Out of memory in FinishLogin (main block)...\n", 0 ); + return STATUS_INSUFFICIENT_RESOURCES; + + } + + pbEncRandSeedHead = ( PENC_BLOCK_HDR ) ( pbRandomBytes + LOG_DATA_POOL_SIZE ); + pbServerResponse = ( pbRandomBytes + LOG_DATA_POOL_SIZE + PACKET_POOL_SIZE ); + + // + // Start working on the login data. As is common in the crypto world, we + // generate a random seed and then make a key from it to be used with a + // bulk cipher algorithm. In Netware land, we use MAC to make an 8 byte + // key from the random seed and use 64bit RC2 as our bulk cipher. We then + // RSA encrypt the seed using the server's public RSA key and use the bulk + // cipher to encrypt the rest of our login data. + // + // Since Novell uses 64bit RC2, the security isn't great. + // + + GenRandomBytes( pbRandomBytes, RAND_KEY_DATA_LEN ); + GenKey8( pbRandomBytes, RAND_KEY_DATA_LEN, RandRC2SecretKey ); + + // + // Now work on the actual packet data. Create the header for the + // encrypted random seed and pack the seed into it. + // + + pbEncRandSeed = ( ( BYTE * )pbEncRandSeedHead ) + sizeof( ENC_BLOCK_HDR ); + + pbEncRandSeedHead->cipherLength = RSAGetInputBlockSize( pbServerPublicBsafeKey, + cbServerPublicBsafeKeyLen ); + + cbPackedRandSeedLen = RSAPack( pbRandomBytes, + RAND_KEY_DATA_LEN, + pbEncRandSeed, + pbEncRandSeedHead->cipherLength ); + // + // We should have packed exactly one block. + // + + if( cbPackedRandSeedLen != pbEncRandSeedHead->cipherLength ) { + DebugTrace( 0, Dbg, "RSAPack didn't pack exactly one block!\n", 0 ); + } + + pbEncRandSeedHead->cipherLength = RSAPublic( pbServerPublicBsafeKey, + cbServerPublicBsafeKeyLen, + pbEncRandSeed, + pbEncRandSeedHead->cipherLength, + pbEncRandSeed ); + + if ( !pbEncRandSeedHead->cipherLength ) { + + DebugTrace( 0, Dbg, "Failing in FinishLogin... encryption failed.\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + + } + + // + // Fill in the rest of the header for the random seed. We don't count + // the first DWORD in the EBH; it isn't part of the header as netware + // wants it, per se. + // + + pbEncRandSeedHead->blockLength = pbEncRandSeedHead->cipherLength + + sizeof( ENC_BLOCK_HDR ) - + sizeof( DWORD ); + pbEncRandSeedHead->version = 1; + pbEncRandSeedHead->encType = ENC_TYPE_RSA_PUBLIC; + pbEncRandSeedHead->dataLength = RAND_KEY_DATA_LEN; + + // + // Go back to working on the login data. Fill out the rbb. + // + + psRandByteBlock = ( PNDS_RAND_BYTE_BLOCK ) ( pbRandomBytes + RAND_KEY_DATA_LEN ); + + GenRandomBytes( (BYTE *) &psRandByteBlock->rand1, 4 ); + psRandByteBlock->rand2Len = RAND_FL_DATA_LEN; + GenRandomBytes( (BYTE *) &psRandByteBlock->rand2[0], RAND_FL_DATA_LEN ); + + // + // Fill out the header for the encrypted challenge right after the rbb. + // + + pbEncChallengeHead = (ENC_BLOCK_HDR *) ( ((BYTE *)psRandByteBlock) + + sizeof( NDS_RAND_BYTE_BLOCK ) ); + + pbEncChallengeHead->version = 1; + pbEncChallengeHead->encType = ENC_TYPE_RC2_CBC; + pbEncChallengeHead->dataLength = 4; + pbEncChallengeHead->cipherLength = cbEncryptedChallengeLen; + pbEncChallengeHead->blockLength = cbEncryptedChallengeLen + + sizeof( ENC_BLOCK_HDR ) - + sizeof( DWORD ); + + // + // Place the encrypted challenge immediately after its header. + // + + RtlCopyMemory( (BYTE *)( ((BYTE *)pbEncChallengeHead) + + sizeof( ENC_BLOCK_HDR )), + pbEncryptedChallenge, + cbEncryptedChallengeLen ); + + // + // Prepare the RC2 key to decrypt FinishLogin response. + // + + GenKey8( (BYTE *)( &pbEncChallengeHead->version ), + pbEncChallengeHead->blockLength, + pbEncryptedChallengeKey ); + + // + // Finish up the packet data by preparing the login data. Start + // with the encryption header. + // + + pbEncLogDataHead = ( PENC_BLOCK_HDR ) ( pbEncRandSeed + + ROUNDUP4( pbEncRandSeedHead->cipherLength ) ); + + pbEncLogData = ( ( BYTE * )pbEncLogDataHead ) + sizeof( ENC_BLOCK_HDR ); + + pbEncLogDataHead->version = 1; + pbEncLogDataHead->encType = ENC_TYPE_RC2_CBC; + pbEncLogDataHead->dataLength = sizeof( NDS_RAND_BYTE_BLOCK ) + + sizeof( ENC_BLOCK_HDR ) + + cbEncryptedChallengeLen; + + // + // Sanity check the packet pool for overflow. + // + + if ( ( pbEncLogData + pbEncLogDataHead->dataLength + ( 2 * CIPHERBLOCKSIZE ) ) - + (BYTE *) pbEncRandSeedHead > PACKET_POOL_SIZE ) { + + DebugTrace( 0, Dbg, "Packet pool overflow... I'd better fix this.\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + // + // Encrypt the login data. + // + + CryptStatus = CBCEncrypt( RandRC2SecretKey, + NULL, + (BYTE *)psRandByteBlock, + pbEncLogDataHead->dataLength, + pbEncLogData, + &CryptLen, + BSAFE_CHECKSUM_LEN ); + + if ( CryptStatus ) { + + DebugTrace( 0, Dbg, "Encryption failure in FinishLogin...\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + pbEncLogDataHead->cipherLength = (WORD)CryptLen; + pbEncLogDataHead->blockLength = pbEncLogDataHead->cipherLength + + sizeof( ENC_BLOCK_HDR ) - + sizeof( DWORD ); + + // + // We can finally send out the finish login request! Calculate the + // send amount and make the request. + // + + RequestPacketLen = ( (BYTE *) pbEncLogData + pbEncLogDataHead->cipherLength ) - + (BYTE *) pbEncRandSeedHead; + + NdsRequest.pRecvBufferVa = pbServerResponse; + NdsRequest.dwRecvLen = RESP_POOL_SIZE; + NdsRequest.pRecvMdl = NULL; + + NdsRequest.pRecvMdl = ALLOCATE_MDL( pbServerResponse, + RESP_POOL_SIZE, + FALSE, + FALSE, + NULL ); + if ( !NdsRequest.pRecvMdl ) { + Status = STATUS_INSUFFICIENT_RESOURCES; + goto ExitWithCleanup; + } + + MmProbeAndLockPages( NdsRequest.pRecvMdl, + KernelMode, + IoWriteAccess ); + + Status = FragExWithWait( pIrpContext, + NDSV_FINISH_LOGIN, + &NdsRequest, + "DDDDDDDr", + 2, // Version + dwLoginFlags, // Flags + dwUserOID, // Entry ID + 0x494, // + 1, // Security Version + 0x20009, // Envelope ID 1 + 0x488, // Envelope length + pbEncRandSeedHead, // Cipher block + RequestPacketLen ); // Cipher block length + + MmUnlockPages( NdsRequest.pRecvMdl ); + FREE_MDL( NdsRequest.pRecvMdl ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + Status = NdsCompletionCodetoNtStatus( &NdsRequest ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + if ( Status == NWRDR_PASSWORD_HAS_EXPIRED ) { + PasswordExpired = TRUE; + } + + cbServerRespLen = NdsRequest.dwBytesWritten; + + // + // Save the credential validity times. + // + + Status = ParseResponse( NULL, + pbServerResponse, + cbServerRespLen, + "G_DD", + sizeof( DWORD ), + pdwCredentialStartTime, + pdwCredentialEndTime ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + // + // Grab the encryption block header for the response. This response in + // RC2 encrypted with the pbEncryptedChallengeKey. + // + + pbEncServerRespHead = (ENC_BLOCK_HDR *) ( pbServerResponse + + ( 3 * sizeof( DWORD ) ) ); + + if ( pbEncServerRespHead->encType != ENC_TYPE_RC2_CBC || + pbEncServerRespHead->cipherLength > + ( RESP_POOL_SIZE + sizeof( ENC_BLOCK_HDR ) + 12 ) ) { + + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + // + // Decrypt the server response in place. + // + + pbEncServerResp = ( BYTE * ) ( ( BYTE * ) pbEncServerRespHead + + sizeof( ENC_BLOCK_HDR ) ); + + CryptStatus = CBCDecrypt( pbEncryptedChallengeKey, + NULL, + pbEncServerResp, + pbEncServerRespHead->cipherLength, + pbEncServerResp, + &cbServerRespLen, + BSAFE_CHECKSUM_LEN ); + + if ( CryptStatus || + cbServerRespLen != pbEncServerRespHead->dataLength ) { + + DebugTrace( 0, Dbg, "Encryption failure (2) in FinishLogin...\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + // + // Examine the first random number to make sure the server is authentic. + // + + if ( psRandByteBlock->rand1 != * ( DWORD * ) pbEncServerResp ) { + + DebugTrace( 0, Dbg, "Server failed to respond to our challenge correctly...\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + + } + + // + // We know things are legit, so we can extract the private key. + // Careful, though: don't XOR the size dword. + // + + pbEncServerResp += sizeof( DWORD ); + EncKeyLen = * ( DWORD * ) ( pbEncServerResp ); + + pbEncServerResp += sizeof( DWORD ); + while ( EncKeyLen-- ) { + + pbEncServerResp[EncKeyLen] ^= psRandByteBlock->rand2[EncKeyLen]; + } + + // + // Check the encryption header on the private key. Don't forget to + // backup to include the size dword. + // + + pbPrivKeyHead = ( ENC_BLOCK_HDR * )( pbEncServerResp - sizeof( DWORD ) ) ; + + if ( pbPrivKeyHead->encType != ENC_TYPE_RC2_CBC ) { + + DebugTrace( 0, Dbg, "Bad encryption header on the private key...\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + + } + + // + // Finally, copy out the user's private NDS key. + // + + if ( *pcbUserEncPrivateNdsKeyLen >= pbPrivKeyHead->cipherLength ) { + + DebugTrace( 0, Dbg, "Encrypted private key len: %d\n", + pbPrivKeyHead->cipherLength ); + + RtlCopyMemory( pbUserEncPrivateNdsKey, + ((BYTE *)( pbPrivKeyHead )) + sizeof( ENC_BLOCK_HDR ), + pbPrivKeyHead->cipherLength ); + + *pcbUserEncPrivateNdsKeyLen = pbPrivKeyHead->cipherLength; + + Status = STATUS_SUCCESS; + + } else { + + DebugTrace( 0, Dbg, "Encryption failure on private key in FinishLogin...\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + + } + +ExitWithCleanup: + + FREE_POOL( pbRandomBytes ); + + if ( ( NT_SUCCESS( Status ) ) && + ( PasswordExpired ) ) { + Status = NWRDR_PASSWORD_HAS_EXPIRED; + } + + return Status; + +} + +NTSTATUS +ChangeNdsPassword( + PIRP_CONTEXT pIrpContext, + DWORD dwUserOID, + DWORD dwChallenge, + PBYTE pbOldPwHash, + PBYTE pbNewPwHash, + PNDS_PRIVATE_KEY pUserPrivKey, + PBYTE pServerPublicBsafeKey, + UINT ServerPubKeyLen +) +/*+++ + +Description: + + Send a change password packet. Change the users password + on the NDS tree that this irp context points to. + +Arguments: + + pIrpContext - The irp context for this request. Points to the target server. + dwUserOID - The oid for the current user. + dwChallenge - The encrypted challenge from begin login. + pbOldPwHash - The 16 byte hash of the old password. + pbNewPwHash - The 16 byte hash of the new password. + pUserPrivKey - The user's private RSA key with NDS header. + pServerPublicBsafeKey - The server's public RSA key in BSAFE format. + ServerPubKeyLen - The length of the server's public BSAFE key. + +--*/ +{ + NTSTATUS Status; + BYTE pbNewPwKey[8]; + BYTE pbSecretKey[8]; + PENC_BLOCK_HDR pbEncSecretKey, pbEncChangePassReq; + BYTE RandomBytes[RAND_KEY_DATA_LEN]; + PBYTE pbEncData; + PNDS_CHPW_MSG pChangePassMsg; + INT CryptStatus, CryptLen; + DWORD dwTotalEncDataLen; + LOCKED_BUFFER NdsRequest; + + PAGED_CODE(); + + // + // Create a secret key from the new password. + // + + GenKey8( pbNewPwHash, 16, pbNewPwKey ); + + pbEncSecretKey = ALLOCATE_POOL( PagedPool, + ( ( 2 * sizeof( ENC_BLOCK_HDR ) ) + + ( MAX_RSA_BYTES ) + + ( sizeof( NDS_CHPW_MSG ) ) + + ( sizeof( NDS_PRIVATE_KEY ) ) + + ( pUserPrivKey->keyDataLength ) + + 16 ) ); + + if ( !pbEncSecretKey ) { + DebugTrace( 0, Dbg, "ChangeNdsPassword: Out of memory.\n", 0 ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + Status = NdsAllocateLockedBuffer( &NdsRequest, NDS_BUFFER_SIZE ); + + if ( !NT_SUCCESS( Status ) ) { + FREE_POOL( pbEncSecretKey ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + // + // Generate a random key. + // + + GenRandomBytes( RandomBytes, RAND_KEY_DATA_LEN ); + GenKey8( RandomBytes, RAND_KEY_DATA_LEN, pbSecretKey ); + + // + // Encrypt the secret key data in the space after the EBH. + // + + pbEncSecretKey->dataLength = RAND_KEY_DATA_LEN; + pbEncSecretKey->cipherLength = RSAGetInputBlockSize( pServerPublicBsafeKey, ServerPubKeyLen); + + pbEncData = ( PBYTE ) ( pbEncSecretKey + 1 ); + + pbEncSecretKey->cipherLength = RSAPack( RandomBytes, + pbEncSecretKey->dataLength, + pbEncData, + pbEncSecretKey->cipherLength ); + + pbEncSecretKey->cipherLength = RSAPublic( pServerPublicBsafeKey, + ServerPubKeyLen, + pbEncData, + pbEncSecretKey->cipherLength, + pbEncData ); + + if ( !pbEncSecretKey->cipherLength ) { + DebugTrace( 0, Dbg, "ChangeNdsPassword: RSA encryption failed.\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + // + // Finish filling out the EBH for the secret key block. + // + + pbEncSecretKey->version = 1; + pbEncSecretKey->encType = ENC_TYPE_RSA_PUBLIC; + pbEncSecretKey->blockLength = pbEncSecretKey->cipherLength + + sizeof( ENC_BLOCK_HDR ) - + sizeof( DWORD ); + + // + // Now form the change password request. + // + + pbEncChangePassReq = ( PENC_BLOCK_HDR ) + ( pbEncData + ROUNDUP4( pbEncSecretKey->cipherLength ) ); + + pChangePassMsg = ( PNDS_CHPW_MSG ) ( pbEncChangePassReq + 1 ); + + // + // Init the Change Password message. + // + + pChangePassMsg->challenge = dwChallenge; + pChangePassMsg->oldPwLength = pChangePassMsg->newPwLength = 16; + + RtlCopyMemory( pChangePassMsg->oldPwHash, pbOldPwHash, pChangePassMsg->oldPwLength ); + RtlCopyMemory( pChangePassMsg->newPwHash, pbNewPwHash, pChangePassMsg->newPwLength ); + + pChangePassMsg->unknown = 8; + pChangePassMsg->encPrivKeyHdr.version = 1; + pChangePassMsg->encPrivKeyHdr.encType = ENC_TYPE_RC2_CBC; + pChangePassMsg->encPrivKeyHdr.dataLength = pUserPrivKey->keyDataLength + sizeof( NDS_PRIVATE_KEY ); + + // + // Encrypt the private key with the key derived from the new password. + // + + CryptStatus = CBCEncrypt( pbNewPwKey, + NULL, + ( PBYTE ) pUserPrivKey, + pChangePassMsg->encPrivKeyHdr.dataLength, + ( PBYTE ) ( pChangePassMsg + 1 ), + &CryptLen, + BSAFE_CHECKSUM_LEN ); + + if ( CryptStatus ) { + DebugTrace( 0, Dbg, "ChangeNdsPassword: CBC encrypt failed.\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + // + // Finish filling out the encryption header. + // + + pChangePassMsg->encPrivKeyHdr.cipherLength = CryptLen; + pChangePassMsg->encPrivKeyHdr.blockLength = CryptLen + + sizeof( ENC_BLOCK_HDR ) - + sizeof( DWORD ); + pbEncChangePassReq->version = 1; + pbEncChangePassReq->encType = ENC_TYPE_RC2_CBC; + pbEncChangePassReq->dataLength = sizeof( NDS_CHPW_MSG ) + CryptLen; + + // + // Encrypt the whole Change Password message in-place with the secret key. + // + + CryptStatus = CBCEncrypt( pbSecretKey, + NULL, + ( PBYTE ) pChangePassMsg, + pbEncChangePassReq->dataLength, + ( PBYTE ) pChangePassMsg, + &CryptLen, + BSAFE_CHECKSUM_LEN); + + if ( CryptStatus ) { + DebugTrace( 0, Dbg, "ChangeNdsPassword: Second CBC encrypt failed.\n", 0 ); + Status = STATUS_UNSUCCESSFUL; + goto ExitWithCleanup; + } + + pbEncChangePassReq->cipherLength = CryptLen; + pbEncChangePassReq->blockLength = + CryptLen + sizeof( ENC_BLOCK_HDR ) - sizeof( DWORD ); + + // + // Calculate the size of the request. + // + + dwTotalEncDataLen = sizeof( ENC_BLOCK_HDR ) + // Secret key header. + ROUNDUP4( pbEncSecretKey->cipherLength ) + // Secret key data. + sizeof( ENC_BLOCK_HDR ) + // Change pass msg header. + CryptLen; // Change pass data. + + // + // Send this change password message to the server. + // + + Status = FragExWithWait( pIrpContext, + NDSV_CHANGE_PASSWORD, + &NdsRequest, + "DDDDDDr", + 0, + dwUserOID, + dwTotalEncDataLen + ( 3 * sizeof( DWORD ) ), + 1, + 0x20009, + dwTotalEncDataLen, + pbEncSecretKey, + dwTotalEncDataLen ); + + if ( NT_SUCCESS( Status ) ) { + Status = NdsCompletionCodetoNtStatus( &NdsRequest ); + } + +ExitWithCleanup: + + FREE_POOL( pbEncSecretKey ); + NdsFreeLockedBuffer( &NdsRequest ); + return Status; + +} + +NTSTATUS +NdsServerAuthenticate( + IN PIRP_CONTEXT pIrpContext, + IN PNDS_SECURITY_CONTEXT pNdsContext +) +/*++ + +Routine Description: + + Authenticate an NDS connection. + The user must have already logged into the NDS tree. + + If you change this function - know that you cannot + at any point try to acquire the nds credential + resource exclusive from here since that could cause + a dead lock!!! + + You also must not dequeue the irp context! + +Arguments: + + pIrpContext - IrpContext for the server that we want to authenticate to. + +Return value: + + NTSTATUS + +--*/ +{ + NTSTATUS Status; + + BYTE *pbUserPublicBsafeKey; + int cbUserPublicBsafeKeyLen; + + NDS_AUTH_MSG *psAuthMsg; + NDS_CREDENTIAL *psLocalCredential; + DWORD dwLocalCredentialLen; + UNICODE_STRING uUserName; + DWORD UserOID; + + BYTE *x, *y, *r; + BYTE CredentialHash[16]; + int i, rsaBlockSize, rsaModSize, totalXLen; + DWORD dwServerRand; + + BYTE *pbResponse; + DWORD cbResponseLen; + LOCKED_BUFFER NdsRequest; + + PAGED_CODE(); + + DebugTrace( 0, Dbg, "Entering NdsServerAuthenticate...\n", 0 ); + + // + // Allocate space for the auth msg, credential, G-Q bytes, and + // the response buffer. + // + + psAuthMsg = ALLOCATE_POOL( PagedPool, + sizeof( NDS_AUTH_MSG ) + // auth message + sizeof( NDS_CREDENTIAL ) + // credential + MAX_NDS_NAME_SIZE + // + ( MAX_RSA_BYTES * 9 ) ); // G-Q rands + + if ( !psAuthMsg ) { + + DebugTrace( 0, Dbg, "Out of memory in NdsServerAuthenticate (0)...\n", 0 ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + pbResponse = ALLOCATE_POOL( PagedPool, NDS_BUFFER_SIZE ); + + if ( !pbResponse ) { + + DebugTrace( 0, Dbg, "Out of memory in NdsServerAuthenticate (1)...\n", 0 ); + FREE_POOL( psAuthMsg ); + return STATUS_INSUFFICIENT_RESOURCES; + } + + psLocalCredential = (PNDS_CREDENTIAL)( ((BYTE *) psAuthMsg) + + sizeof( NDS_AUTH_MSG ) ); + + // + // Locate the public BSAFE key. + // + + cbUserPublicBsafeKeyLen = NdsGetBsafeKey ( (BYTE *)(pNdsContext->PublicNdsKey), + pNdsContext->PublicKeyLen, + &pbUserPublicBsafeKey ); + + // + // Get the user's object Id but do not jump dir servers. There is never + // any optional data, so we don't really need to skip over it. + // + + uUserName.MaximumLength = pNdsContext->Credential->userNameLength; + uUserName.Length = uUserName.MaximumLength; + uUserName.Buffer = ( WCHAR * ) ( ((BYTE *)pNdsContext->Credential) + + sizeof( NDS_CREDENTIAL ) + + pNdsContext->Credential->optDataSize ); + + Status = NdsResolveNameKm( pIrpContext, + &uUserName, + &UserOID, + FALSE, + RSLV_DEREF_ALIASES | RSLV_CREATE_ID | RSLV_ENTRY_ID ); + + if ( !NT_SUCCESS(Status) ) { + goto ExitWithCleanup; + } + + // + // Issue the Begin Authenticate request to get the random server nonce. + // + + Status = BeginAuthenticate( pIrpContext, + UserOID, + &dwServerRand ); + + if ( !NT_SUCCESS(Status) ) { + goto ExitWithCleanup; + } + + // + // Figure out the size of the zero-padded RSA Blocks. We use the same + // size as the modulus field of the public key (typically 56 bytes). + // + + RSAGetModulus( pbUserPublicBsafeKey, + cbUserPublicBsafeKeyLen, + &rsaBlockSize); + + DebugTrace( 0, Dbg, "RSA block size for authentication: %d\n", rsaBlockSize ); + + // + // Prepare the credential and the 3 G-Q rands. The credential, + // xs, and ys go out in the packet; rs is secret. + // + + RtlZeroMemory( ( BYTE * )psLocalCredential, + sizeof( NDS_CREDENTIAL ) + + MAX_NDS_NAME_SIZE + + 9 * rsaBlockSize ); + + dwLocalCredentialLen = sizeof( NDS_CREDENTIAL ) + + pNdsContext->Credential->optDataSize + + pNdsContext->Credential->userNameLength; + + DebugTrace( 0, Dbg, "Credential length is %d.\n", dwLocalCredentialLen ); + + RtlCopyMemory( (BYTE *)psLocalCredential, + pNdsContext->Credential, + dwLocalCredentialLen ); + + x = ( BYTE * ) psAuthMsg + sizeof( NDS_AUTH_MSG ) + dwLocalCredentialLen; + y = x + ( 3 * rsaBlockSize ); + r = y + ( 3 * rsaBlockSize ); + + rsaModSize = RSAGetInputBlockSize( pbUserPublicBsafeKey, + cbUserPublicBsafeKeyLen ); + + DebugTrace( 0, Dbg, "RSA modulus size: %d\n", rsaModSize ); + + for ( i = 0; i < 3; i++ ) { + + // + // Create Random numbers r1, r2 and r3 of modulus size. + // + + GenRandomBytes( r + ( rsaBlockSize * i ), rsaModSize ); + + // + // Compute x = r**e mod N. + // + + RSAPublic( pbUserPublicBsafeKey, + cbUserPublicBsafeKeyLen, + r + ( rsaBlockSize * i ), + rsaModSize, + x + ( rsaBlockSize * i ) ); + + } + + // + // Fill in the AuthMsg fields. + // + + psAuthMsg->version = 0; + psAuthMsg->svrRand = dwServerRand; + psAuthMsg->verb = NDSV_FINISH_AUTHENTICATE; + psAuthMsg->credentialLength = dwLocalCredentialLen; + + // + // MD2 hash the auth message, credential and x's. + // + + MD2( (BYTE *)psAuthMsg, + sizeof( NDS_AUTH_MSG ) + + psAuthMsg->credentialLength + + ( 3 * rsaBlockSize ), + CredentialHash ); + + // + // Compute yi = ri*(S**ci) mod N; c1,c2,c3 are the first three + // 16 bit numbers in CredentialHash. + // + + totalXLen = 3 * rsaBlockSize; + + for ( i = 0; i < 3; i++ ) { + + RSAModExp( pbUserPublicBsafeKey, + cbUserPublicBsafeKeyLen, + ( (BYTE *)(pNdsContext->Signature) ) + sizeof( NDS_SIGNATURE ), + pNdsContext->Signature->signDataLength, + &CredentialHash[i * sizeof( WORD )], + sizeof( WORD ), + y + ( rsaBlockSize * i) ); + + RSAModMpy( pbUserPublicBsafeKey, + cbUserPublicBsafeKeyLen, + y + ( rsaBlockSize * i ), // input1 = S**ci mod N + rsaModSize + 1, + r + ( rsaBlockSize * i ), // input2 = ri + rsaModSize, + y + ( rsaBlockSize * i ) ); // output = yi + } + + // + // Send the auth proof. + // + + NdsRequest.pRecvBufferVa = pbResponse; + NdsRequest.dwRecvLen = NDS_BUFFER_SIZE; + NdsRequest.pRecvMdl = NULL; + + NdsRequest.pRecvMdl = ALLOCATE_MDL( pbResponse, + NDS_BUFFER_SIZE, + FALSE, + FALSE, + NULL ); + if ( !NdsRequest.pRecvMdl ) { + Status = STATUS_INSUFFICIENT_RESOURCES; + goto ExitWithCleanup; + } + + MmProbeAndLockPages( NdsRequest.pRecvMdl, + KernelMode, + IoWriteAccess ); + + Status = FragExWithWait( pIrpContext, + NDSV_FINISH_AUTHENTICATE, + &NdsRequest, + "DDDrDDWWWWr", + 0, // version + 0, // sessionKeyLen + psAuthMsg->credentialLength, // credential len + (BYTE *)psLocalCredential, // actual credential + ROUNDUP4( psAuthMsg->credentialLength ), + 12 + ( totalXLen * 2 ), // length of remaining + 1, // proof version? + 8, // tag? + 16, // message digest base + 3, // proof order + totalXLen, // proofOrder*sizeof(x) + x, // x1,x2,x3,y1,y2,y3 + 2 * totalXLen ); + + MmUnlockPages( NdsRequest.pRecvMdl ); + FREE_MDL( NdsRequest.pRecvMdl ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + Status = NdsCompletionCodetoNtStatus( &NdsRequest ); + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + cbResponseLen = NdsRequest.dwBytesWritten; + DebugTrace( 0, Dbg, "Authentication returned ok status.\n", 0 ); + + // + // We completed NDS authentication, so clear out the name + // and password in the SCB so that we use the credentials + // from now on. + // + + if ( pIrpContext->pScb->UserName.Buffer != NULL ) { + + DebugTrace( 0, Dbg, "Clearing out bindery login data.\n", 0 ); + + pIrpContext->pScb->UserName.Length = 0; + pIrpContext->pScb->UserName.MaximumLength = 0; + + pIrpContext->pScb->Password.Length = 0; + pIrpContext->pScb->Password.MaximumLength = 0; + + FREE_POOL( pIrpContext->pScb->UserName.Buffer ); + RtlInitUnicodeString( &pIrpContext->pScb->UserName, NULL ); + RtlInitUnicodeString( &pIrpContext->pScb->Password, NULL ); + + } + +ExitWithCleanup: + + FREE_POOL( psAuthMsg ); + FREE_POOL( pbResponse ); + + return Status; +} + +NTSTATUS +BeginAuthenticate( + IN PIRP_CONTEXT pIrpContext, + IN DWORD dwUserId, + OUT DWORD *pdwSvrRandom +) +/*++ + +Routine Description: + + Authenticate an NDS connection. + The user must have already logged into the NDS tree. + +Arguments: + + pIrpContext - IrpContext for the server that we want to authenticate to. + dwUserID - The user OID that we are authenticating ourselves as. + pdwSvrRandon - The server random challenge. + +Return value: + + NTSTATUS - The result of the operation. + +--*/ +{ + NTSTATUS Status; + LOCKED_BUFFER NdsRequest; + + DWORD dwClientRand; + + PAGED_CODE(); + + Status = NdsAllocateLockedBuffer( &NdsRequest, NDS_BUFFER_SIZE ); + + if ( !NT_SUCCESS( Status ) ) { + return STATUS_INSUFFICIENT_RESOURCES; + } + + GenRandomBytes( (BYTE *)&dwClientRand, sizeof( dwClientRand ) ); + + Status = FragExWithWait( pIrpContext, + NDSV_BEGIN_AUTHENTICATE, + &NdsRequest, + "DDD", + 0, // Version. + dwUserId, // Entry Id. + dwClientRand ); // Client's random challenge. + + if ( !NT_SUCCESS( Status ) ) { + goto ExitWithCleanup; + } + + Status = NdsCompletionCodetoNtStatus( &NdsRequest ); + + if ( !NT_SUCCESS( Status ) ) { + + if ( Status == STATUS_BAD_NETWORK_PATH ) { + Status = STATUS_NO_SUCH_USER; + } + + goto ExitWithCleanup; + } + + // + // The reply actually contains all this, even though we don't look at it? + // + // typedef struct { + // DWORD svrRand; + // DWORD totalLength; + // TAG_DATA_HEADER tdh; + // WORD unknown; // Always 2. + // DWORD encClientRandLength; + // CIPHER_BLOCK_HEADER keyCipherHdr; + // BYTE keyCipher[]; + // CIPHER_BLOCK_HEADER encClientRandHdr; + // BYTE encClientRand[]; + // } REPLY_BEGIN_AUTHENTICATE; + // + // Nah, that can't be right. + // + + Status = ParseResponse( NULL, + NdsRequest.pRecvBufferVa, + NdsRequest.dwBytesWritten, + "G_D", + sizeof( DWORD ), + pdwSvrRandom ); + + // + // We either got it or we didn't. + // + +ExitWithCleanup: + + NdsFreeLockedBuffer( &NdsRequest ); + return Status; +} + +NTSTATUS +NdsLicenseConnection( + PIRP_CONTEXT pIrpContext +) +/*+++ + + Send the license NCP to the server to license this connection. + +---*/ +{ + + NTSTATUS Status; + + PAGED_CODE(); + + DebugTrace( 0, Dbg, "Licensing connection to %wZ.\n", &(pIrpContext->pNpScb->pScb->UidServerName) ); + + // + // Change the authentication state of the connection. + // + + Status = ExchangeWithWait ( pIrpContext, + SynchronousResponseCallback, + "SD", + NCP_ADMIN_FUNCTION, + NCP_CHANGE_CONN_AUTH_STATUS, + NCP_CONN_LICENSED ); + + if ( !NT_SUCCESS( Status ) ) { + DebugTrace( 0, Dbg, "Licensing failed to %wZ.\n", &(pIrpContext->pNpScb->pScb->UidServerName) ); + } + + return Status; + +} + +NTSTATUS +NdsUnlicenseConnection( + PIRP_CONTEXT pIrpContext +) +/*+++ + + Send the license NCP to the server to license this connection. + +---*/ +{ + + NTSTATUS Status; + + PAGED_CODE(); + + DebugTrace( 0, Dbg, "Unlicensing connection to %wZ.\n", &(pIrpContext->pNpScb->pScb->UidServerName) ); + + // + // Change the authentication state of the connection. + // + + Status = ExchangeWithWait ( pIrpContext, + SynchronousResponseCallback, + "SD", + NCP_ADMIN_FUNCTION, + NCP_CHANGE_CONN_AUTH_STATUS, + NCP_CONN_NOT_LICENSED ); + + if ( !NT_SUCCESS( Status ) ) { + DebugTrace( 0, Dbg, "Unlicensing failed to %wZ.\n", &(pIrpContext->pNpScb->pScb->UidServerName) ); + } + + return Status; +} + +int +NdsGetBsafeKey( + UCHAR *pPubKey, + const int pubKeyLen, + UCHAR **ppBsafeKey +) +/*++ + +Routine Description: + + Locate the BSAFE key from within the public key. Note that this does + not work for private keys in NDS format. For private keys, you just + skip the size word. + + This is verbatim from Win95. + +Routine Arguments: + + pPubKey - A pointer to the public key. + pubKeyLen - The length of the public key. + ppBsafeKey - The pointer to the BSAFE key in the public key. + +Return Value: + + The length of the BSAFE key. + +--*/ +{ + int bsafePubKeyLen = 0, totalDNLen; + char *pRcv; + NTSTATUS Status; + + PAGED_CODE(); + + totalDNLen = 0; + Status = ParseResponse( NULL, + pPubKey, + pubKeyLen, + "G_W", + ( 2 * sizeof( DWORD ) ) + sizeof( WORD ), + &totalDNLen ); + + if ( !NT_SUCCESS(Status) ) { + goto Exit; + } + + Status = ParseResponse( NULL, + pPubKey, + pubKeyLen - 12, + "G__W", + 12, + 5 * sizeof( WORD ) + + 3 * sizeof( DWORD ) + + totalDNLen, + &bsafePubKeyLen ); + + if ( !NT_SUCCESS(Status) ) { + goto Exit; + } + + *ppBsafeKey = (UCHAR *) pPubKey + + 14 + + 5 * sizeof( WORD ) + + 3 * sizeof( DWORD ) + + totalDNLen; + + +Exit: + + return bsafePubKeyLen; +} + +NTSTATUS +NdsLogoff( + IN PIRP_CONTEXT pIrpContext +) +/*++ + +Routine Description: + + Sends a logout to the NDS tree, closes all NDS authenticated + connections, and destroys the current set of NDS credentials. + + This routine acquires the credential list exclusive. + +Arguments: + + pIrpContext - The IRP context for this request pointed to a + valid dir server. + +Notes: + + This is only called from DeleteConnection. The caller owns + the RCB exclusive and we will free it before returning. + +--*/ +{ + NTSTATUS Status; + LOCKED_BUFFER NdsRequest; + PNDS_SECURITY_CONTEXT pCredentials; + PLOGON pLogon; + PSCB pScb; + PNONPAGED_SCB pNpScb; + + PLIST_ENTRY ScbQueueEntry; + PNONPAGED_SCB pNextNpScb; + KIRQL OldIrql; + + // + // Grab the user's LOGON structure. + // + + pNpScb = pIrpContext->pNpScb; + pScb = pNpScb->pScb; + + // + // The caller owns the RCB. + // + + pLogon = FindUser( &pScb->UserUid, FALSE ); + + if ( !pLogon ) { + DebugTrace( 0, Dbg, "Invalid security context for NdsLogoff.\n", 0 ); + NwReleaseRcb( &NwRcb ); + NwDequeueIrpContext( pIrpContext, FALSE ); + return STATUS_NO_SUCH_USER; + } + + // + // Check to make sure that we have something to log off from. + // + + Status = NdsLookupCredentials( &pScb->NdsTreeName, + pLogon, + &pCredentials, + CREDENTIAL_WRITE, + FALSE ); + + if ( !NT_SUCCESS( Status )) { + DebugTrace( 0, Dbg, "NdsLogoff: Nothing to log off from.\n", 0 ); + NwReleaseRcb( &NwRcb ); + NwDequeueIrpContext( pIrpContext, FALSE ); + return STATUS_NO_SUCH_LOGON_SESSION; + } + + // + // If the credentials are locked, then someone is already + // doing a logout. + // + + if ( pCredentials->CredentialLocked ) { + DebugTrace( 0, Dbg, "NdsLogoff: Logoff already in progress.\n", 0 ); + NwReleaseCredList( pLogon ); + NwReleaseRcb( &NwRcb ); + NwDequeueIrpContext( pIrpContext, FALSE ); + return STATUS_DEVICE_BUSY; + } + + // + // Mark the credential locked so we can logout without + // worrying about others logging in. + // + + pCredentials->CredentialLocked = TRUE; + + // + // Release all our resoures so we can jump around servers. + // + + NwReleaseCredList( pLogon ); + NwReleaseRcb( &NwRcb ); + NwDequeueIrpContext( pIrpContext, FALSE ); + + // + // Look through the scb list for connections that are in use. If all + // existing connections can be closed down, then we can complete the logout. + // + + KeAcquireSpinLock( &ScbSpinLock, &OldIrql ); + + ScbQueueEntry = pNpScb->ScbLinks.Flink; + + if ( ScbQueueEntry == &ScbQueue ) { + ScbQueueEntry = ScbQueue.Flink; + } + + pNextNpScb = CONTAINING_RECORD( ScbQueueEntry, NONPAGED_SCB, ScbLinks ); + + NwReferenceScb( pNextNpScb ); + KeReleaseSpinLock( &ScbSpinLock, OldIrql ); + + while ( pNextNpScb != pNpScb ) { + + if ( pNextNpScb->pScb != NULL ) { + + // + // Is this connection in use by us and is it NDS authenticated? + // + + if ( RtlEqualUnicodeString( &pScb->NdsTreeName, + &pNextNpScb->pScb->NdsTreeName, + TRUE ) && + ( pScb->UserUid.QuadPart == pNextNpScb->pScb->UserUid.QuadPart ) && + ( pNextNpScb->State == SCB_STATE_IN_USE ) && + ( pNextNpScb->pScb->UserName.Length == 0 ) ) { + + pIrpContext->pNpScb = pNextNpScb; + pIrpContext->pScb = pNextNpScb->pScb; + NwAppendToQueueAndWait( pIrpContext ); + + if ( pNextNpScb->pScb->OpenFileCount == 0 ) { + + // + // Can we close it anyway? Should we check + // for open handles and the such here? + // + + pNextNpScb->State = SCB_STATE_LOGIN_REQUIRED; + NwDequeueIrpContext( pIrpContext, FALSE ); + + } else { + + DebugTrace( 0, Dbg, "NdsLogoff: Other connections in use.\n", 0 ); + + NwAcquireExclusiveCredList( pLogon ); + pCredentials->CredentialLocked = FALSE; + NwReleaseCredList( pLogon ); + + NwDereferenceScb( pNextNpScb ); + NwDequeueIrpContext( pIrpContext, FALSE ); + + return STATUS_CONNECTION_IN_USE; + + } + } + + } + + // + // Select the next scb. + // + + KeAcquireSpinLock( &ScbSpinLock, &OldIrql ); + + ScbQueueEntry = pNextNpScb->ScbLinks.Flink; + + if ( ScbQueueEntry == &ScbQueue ) { + ScbQueueEntry = ScbQueue.Flink; + } + + NwDereferenceScb( pNextNpScb ); + pNextNpScb = CONTAINING_RECORD( ScbQueueEntry, NONPAGED_SCB, ScbLinks ); + + NwReferenceScb( pNextNpScb ); + KeReleaseSpinLock( &ScbSpinLock, OldIrql ); + + } + + NwDereferenceScb( pNpScb ); + + // + // The seed scb for the tree logout should be valid. + // + + ASSERT( pNpScb->State == SCB_STATE_IN_USE ); + + // + // Check to make sure we can close the host scb. + // + + if ( pScb->OpenFileCount != 0 ) { + + DebugTrace( 0, Dbg, "NdsLogoff: Seed connection in use.\n", 0 ); + + NwAcquireExclusiveCredList( pLogon ); + pCredentials->CredentialLocked = FALSE; + NwReleaseCredList( pLogon ); + + return STATUS_CONNECTION_IN_USE; + } + + // + // We can actually do the logout, so remove the credentials from + // the resource list, release the resource, and logout. + // + // If we are deleting the preferred tree credentials, + // then we need to clear the preferred server. + // + + if ( (pLogon->NdsCredentialList).Flink == &(pCredentials->Next) ) { + + if ( pLogon->ServerName.Buffer != NULL ) { + + DebugTrace( 0, Dbg, "Clearing preferred server at logout time.\n", 0 ); + + FREE_POOL( pLogon->ServerName.Buffer ); + pLogon->ServerName.Length = pLogon->ServerName.MaximumLength = 0; + pLogon->ServerName.Buffer = NULL; + + } + } + + NwAcquireExclusiveCredList( pLogon ); + RemoveEntryList( &pCredentials->Next ); + NwReleaseCredList( pLogon ); + + FreeNdsContext( pCredentials ); + + pIrpContext->pNpScb = pNpScb; + pIrpContext->pScb = pScb; + + Status = NdsAllocateLockedBuffer( &NdsRequest, NDS_BUFFER_SIZE ); + + if ( NT_SUCCESS( Status ) ) { + + Status = FragExWithWait( pIrpContext, + NDSV_LOGOUT, + &NdsRequest, + NULL ); + + NdsFreeLockedBuffer( &NdsRequest ); + + } + + NwAppendToQueueAndWait( pIrpContext ); + + ASSERT( pScb->UserName.Buffer == NULL ); + pNpScb->State = SCB_STATE_LOGIN_REQUIRED; + + NwDequeueIrpContext( pIrpContext, FALSE ); + + return STATUS_SUCCESS; + +} + |