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