/*++ Copyright (c) 1993 Microsoft Corporation Module Name: logon.c Abstract: This module contains the routines called by MSV1_0 authentication package. Author: Yi-Hsin Sung (yihsins) Andy Herron (andyhe) 06-Jun-1994 Added support for MSV1_0 subauthority Revision History: Andy Herron (andyhe) 15-Aug-1994 Pulled out (older) unused MSV1_0 subauthority routines. --*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RESPONSE_SIZE 8 #define WKSTA_ADDRESS_SIZE 20 #define NET_ADDRESS_SIZE 8 #define NODE_ADDRESS_SIZE 12 #define MSV1_0_PASSTHRU 0x01 #define MSV1_0_GUEST_LOGON 0x02 #ifndef LOGON_SUBAUTH_SESSION_KEY #define LOGON_SUBAUTH_SESSION_KEY 0x40 #endif typedef NTSTATUS (*PF_SamIConnect)( IN PSAMPR_SERVER_NAME ServerName, OUT SAMPR_HANDLE *ServerHandle, IN ACCESS_MASK DesiredAccess, IN BOOLEAN TrustedClient ); typedef NTSTATUS (*PF_SamrOpenUser)( SAMPR_HANDLE DomainHandle, ACCESS_MASK DesiredAccess, ULONG UserId, SAMPR_HANDLE __RPC_FAR *UserHandle); typedef NTSTATUS (*PF_SamrCloseHandle)( SAMPR_HANDLE __RPC_FAR *SamHandle); typedef NTSTATUS (*PF_SamrQueryInformationDomain)( SAMPR_HANDLE DomainHandle, DOMAIN_INFORMATION_CLASS DomainInformationClass, PSAMPR_DOMAIN_INFO_BUFFER __RPC_FAR *Buffer); typedef NTSTATUS (*PF_SamrOpenDomain)( SAMPR_HANDLE ServerHandle, ACCESS_MASK DesiredAccess, PRPC_SID DomainId, SAMPR_HANDLE __RPC_FAR *DomainHandle); typedef NTSTATUS (*PF_SamIAccountRestrictions)( IN SAM_HANDLE UserHandle, IN PUNICODE_STRING LogonWorkstation, IN PUNICODE_STRING Workstations, IN PLOGON_HOURS LogonHours, OUT PLARGE_INTEGER LogoffTime, OUT PLARGE_INTEGER KickoffTime ); typedef VOID (*PF_SamIFree_SAMPR_DOMAIN_INFO_BUFFER )( PSAMPR_DOMAIN_INFO_BUFFER Source, DOMAIN_INFORMATION_CLASS Branch ); typedef NTSTATUS (NTAPI *PF_LsaIQueryInformationPolicyTrusted)( IN POLICY_INFORMATION_CLASS InformationClass, OUT PLSAPR_POLICY_INFORMATION *Buffer ); typedef VOID (NTAPI *PF_LsaIFree_LSAPR_POLICY_INFORMATION )( IN POLICY_INFORMATION_CLASS InformationClass, IN PLSAPR_POLICY_INFORMATION PolicyInformation ); typedef NTSTATUS (NTAPI *PF_LsaIOpenPolicyTrusted)( OUT PLSAPR_HANDLE PolicyHandle ); typedef NTSTATUS (*PF_LsarQueryInformationPolicy)( LSAPR_HANDLE PolicyHandle, POLICY_INFORMATION_CLASS InformationClass, PLSAPR_POLICY_INFORMATION __RPC_FAR *PolicyInformation); PF_SamIConnect pfSamIConnect = NULL; PF_SamrOpenUser pfSamrOpenUser = NULL; PF_SamrCloseHandle pfSamrCloseHandle = NULL; PF_SamrQueryInformationDomain pfSamrQueryInformationDomain = NULL; PF_SamrOpenDomain pfSamrOpenDomain = NULL; PF_SamIAccountRestrictions pfSamIAccountRestrictions = NULL; PF_SamIFree_SAMPR_DOMAIN_INFO_BUFFER pfSamIFree_SAMPR_DOMAIN_INFO_BUFFER = NULL; PF_LsaIQueryInformationPolicyTrusted pfLsaIQueryInformationPolicyTrusted = NULL; PF_LsaIFree_LSAPR_POLICY_INFORMATION pfLsaIFree_LSAPR_POLICY_INFORMATION = NULL; PF_LsaIOpenPolicyTrusted pfLsaIOpenPolicyTrusted = NULL; PF_LsarQueryInformationPolicy pfLsarQueryInformationPolicy = NULL; NTSTATUS LoadSamAndLsa( VOID ) ; NTSTATUS LoadSamAndLsa( VOID ) /*++ Routine Description: This routine loads the SAM/LSA dlls and resolves the entry points we need, to avoid staticly linking to those DLLs which do not expect to be loaded by proceses other than LSA. Arguments: None Return Value: STATUS_SUCCESS: if there was no error. STATUS_DLL_NOT_FOUND: cannot load either DLL STATUS_ENTRYPOINT_NOT_FOUND: cannot get proc address for any of entry points --*/ { static HMODULE hDllSam = NULL ; static HMODULE hDllLsa = NULL ; static NTSTATUS lastStatus = STATUS_SUCCESS ; if (hDllLsa && hDllSam) { return lastStatus ; } if (!(hDllSam = LoadLibrary(L"SAMSRV"))) { return STATUS_DLL_NOT_FOUND ; } if (!(hDllLsa = LoadLibrary(L"LSASRV"))) { (void) FreeLibrary(hDllSam) ; hDllSam = NULL ; return STATUS_DLL_NOT_FOUND ; } pfSamIConnect = (PF_SamIConnect) GetProcAddress(hDllSam,"SamIConnect"); pfSamrOpenUser = (PF_SamrOpenUser) GetProcAddress(hDllSam,"SamrOpenUser"); pfSamrCloseHandle = (PF_SamrCloseHandle) GetProcAddress(hDllSam,"SamrCloseHandle"); pfSamrQueryInformationDomain = (PF_SamrQueryInformationDomain) GetProcAddress(hDllSam,"SamrQueryInformationDomain") ; pfSamrOpenDomain = (PF_SamrOpenDomain) GetProcAddress(hDllSam,"SamrOpenDomain"); pfSamIAccountRestrictions = (PF_SamIAccountRestrictions) GetProcAddress(hDllSam,"SamIAccountRestrictions"); pfSamIFree_SAMPR_DOMAIN_INFO_BUFFER = (PF_SamIFree_SAMPR_DOMAIN_INFO_BUFFER) GetProcAddress(hDllSam,"SamIFree_SAMPR_DOMAIN_INFO_BUFFER"); pfLsaIQueryInformationPolicyTrusted = (PF_LsaIQueryInformationPolicyTrusted) GetProcAddress(hDllLsa,"LsaIQueryInformationPolicyTrusted"); pfLsaIFree_LSAPR_POLICY_INFORMATION = (PF_LsaIFree_LSAPR_POLICY_INFORMATION) GetProcAddress(hDllLsa,"LsaIFree_LSAPR_POLICY_INFORMATION"); pfLsaIOpenPolicyTrusted = (PF_LsaIOpenPolicyTrusted) GetProcAddress(hDllLsa,"LsaIOpenPolicyTrusted"); pfLsarQueryInformationPolicy = (PF_LsarQueryInformationPolicy) GetProcAddress(hDllLsa,"LsarQueryInformationPolicy"); if (!( pfSamIConnect && pfSamrOpenUser && pfSamrCloseHandle && pfSamrQueryInformationDomain && pfSamrOpenDomain && pfSamIAccountRestrictions && pfSamIFree_SAMPR_DOMAIN_INFO_BUFFER && pfLsaIQueryInformationPolicyTrusted && pfLsaIFree_LSAPR_POLICY_INFORMATION && pfLsaIOpenPolicyTrusted && pfLsarQueryInformationPolicy) ) { // // cannot find at least one // lastStatus = STATUS_ENTRYPOINT_NOT_FOUND ; (void) FreeLibrary(hDllSam) ; hDllSam = NULL ; (void) FreeLibrary(hDllLsa) ; hDllLsa = NULL ; } else { lastStatus = STATUS_SUCCESS ; } return lastStatus ; } /////////////////////////////////////////////////////////////////////////////// ULONG MapRidToObjectId( DWORD dwRid, LPWSTR pszUserName, BOOL fNTAS, BOOL fBuiltin ); // // These are never closed once they're opened. This is similar to how // msv1_0 does it. Since there's no callback at shutdown time, we have no // way of knowing when to close them. // HANDLE SamDomainHandle = NULL; SAMPR_HANDLE SamConnectHandle = NULL; LSA_HANDLE LsaPolicyHandle = NULL; // // This is where we store out LSA Secret // BOOLEAN GotSecret = FALSE; UCHAR LsaSecretBuffer[USER_SESSION_KEY_LENGTH + 1]; // // forward declare // BOOLEAN MNSWorkstationValidate ( IN PUNICODE_STRING Workstation, IN PUNICODE_STRING UserParameters ); BOOL GetPasswordExpired( IN LARGE_INTEGER PasswordLastSet, IN LARGE_INTEGER MaxPasswordAge ); NTSTATUS QueryDomainPasswordInfo ( PSAMPR_DOMAIN_INFO_BUFFER *DomainInfo ); VOID Shuffle( UCHAR *achObjectId, UCHAR *szUpperPassword, int iPasswordLen, UCHAR *achOutputBuffer ); NTSTATUS GetNcpSecretKey( CHAR *pchNWSecretKey ); NTSTATUS Msv1_0SubAuthenticationRoutine2 ( IN NETLOGON_LOGON_INFO_CLASS LogonLevel, IN PVOID LogonInformation, IN ULONG Flags, IN PUSER_ALL_INFORMATION UserAll, OUT PULONG WhichFields, OUT PULONG UserFlags, OUT PBOOLEAN Authoritative, OUT PLARGE_INTEGER LogoffTime, OUT PLARGE_INTEGER KickoffTime, OUT PUSER_SESSION_KEY UserSessionKey OPTIONAL ) /*++ Routine Description: The subauthentication routine does cient/server specific authentication of a user. The credentials of the user are passed in addition to all the information from SAM defining the user. This routine decides whether to let the user logon. Arguments: LogonLevel -- Specifies the level of information given in LogonInformation. LogonInformation -- Specifies the description for the user logging on. The LogonDomainName field should be ignored. Flags - Flags describing the circumstances of the logon. MSV1_0_PASSTHRU -- This is a PassThru authenication. (i.e., the user isn't connecting to this machine.) MSV1_0_GUEST_LOGON -- This is a retry of the logon using the GUEST user account. UserAll -- The description of the user as returned from SAM. WhichFields -- Returns which fields from UserAllInfo are to be written back to SAM. The fields will only be written if MSV returns success to it's caller. Only the following bits are valid. USER_ALL_PARAMETERS - Write UserAllInfo->Parameters back to SAM. If the size of the buffer is changed, Msv1_0SubAuthenticationRoutine must delete the old buffer using MIDL_user_free() and reallocate the buffer using MIDL_user_allocate(). UserFlags -- Returns UserFlags to be returned from LsaLogonUser in the LogonProfile. The following bits are currently defined: LOGON_GUEST -- This was a guest logon LOGON_NOENCRYPTION -- The caller didn't specify encrypted credentials LOGON_GRACE_LOGON -- The caller's password has expired but logon was allowed during a grace period following the expiration. LOGON_SUBAUTH_SESSION_KEY - a session key was returned from this logon SubAuthentication packages should restrict themselves to returning bits in the high order byte of UserFlags. However, this convention isn't enforced giving the SubAuthentication package more flexibility. Authoritative -- Returns whether the status returned is an authoritative status which should be returned to the original caller. If not, this logon request may be tried again on another domain controller. This parameter is returned regardless of the status code. LogoffTime - Receives the time at which the user should logoff the system. This time is specified as a GMT relative NT system time. KickoffTime - Receives the time at which the user should be kicked off the system. This time is specified as a GMT relative NT system time. Specify, a full scale positive number if the user isn't to be kicked off. UserSessionKey - If non-null, recives a session key for this logon session. Return Value: STATUS_SUCCESS: if there was no error. STATUS_NO_SUCH_USER: The specified user has no account. STATUS_WRONG_PASSWORD: The password was invalid. STATUS_INVALID_INFO_CLASS: LogonLevel is invalid. STATUS_ACCOUNT_LOCKED_OUT: The account is locked out STATUS_ACCOUNT_DISABLED: The account is disabled STATUS_ACCOUNT_EXPIRED: The account has expired. STATUS_PASSWORD_MUST_CHANGE: Account is marked as Password must change on next logon. STATUS_PASSWORD_EXPIRED: The Password is expired. STATUS_INVALID_LOGON_HOURS - The user is not authorized to logon at this time. STATUS_INVALID_WORKSTATION - The user is not authorized to logon to the specified workstation. --*/ { NTSTATUS status; ULONG UserAccountControl; LARGE_INTEGER LogonTime; WCHAR PropertyFlag; NT_OWF_PASSWORD DecryptedPassword; UCHAR Response[RESPONSE_SIZE]; UNICODE_STRING EncryptedPassword; UNICODE_STRING PasswordDateSet; UNICODE_STRING GraceLoginRemaining; SAMPR_HANDLE UserHandle; LARGE_INTEGER pwSetTime; PSAMPR_DOMAIN_INFO_BUFFER DomainInfo; PSAMPR_USER_INFO_BUFFER userControlInfo; LPWSTR pNewUserParams; int index; UCHAR achK[32]; PNETLOGON_NETWORK_INFO LogonNetworkInfo; PCHAR challenge; BOOLEAN authoritative = TRUE; // important default! ULONG userFlags = 0; // important default! ULONG whichFields = 0; // important default! LARGE_INTEGER logoffTime; LARGE_INTEGER kickoffTime; pNewUserParams = NULL; DomainInfo = NULL; GraceLoginRemaining.Buffer = NULL; PasswordDateSet.Buffer = NULL; EncryptedPassword.Buffer = NULL; userControlInfo = NULL; logoffTime.HighPart = 0x7FFFFFFF; // default to no kickoff and logoffTime.LowPart = 0xFFFFFFFF; // no forced logoff kickoffTime.HighPart = 0x7FFFFFFF; kickoffTime.LowPart = 0xFFFFFFFF; status = LoadSamAndLsa() ; if ( !NT_SUCCESS( status )) { return status ; } (VOID) NtQuerySystemTime( &LogonTime ); // // Check whether the SubAuthentication package supports this type // of logon. // if ( LogonLevel != NetlogonNetworkInformation ) { // // This SubAuthentication package only supports network logons. // status = STATUS_INVALID_INFO_CLASS; goto CleanUp; } // // This SubAuthentication package doesn't support access via machine // accounts. // UserAccountControl = USER_NORMAL_ACCOUNT; // // Local user (Temp Duplicate) accounts are only used on the machine // being directly logged onto. // (Nor are interactive or service logons allowed to them.) // if ( (Flags & MSV1_0_PASSTHRU) == 0 ) { UserAccountControl |= USER_TEMP_DUPLICATE_ACCOUNT; } LogonNetworkInfo = (PNETLOGON_NETWORK_INFO) LogonInformation; // // If the account type isn't allowed, // Treat this as though the User Account doesn't exist. // // This SubAuthentication package doesn't allow guest logons. // if ( ( (UserAccountControl & UserAll->UserAccountControl) == 0 ) || ( Flags & MSV1_0_GUEST_LOGON ) ) { authoritative = FALSE; status = STATUS_NO_SUCH_USER; goto CleanUp; } // // Ensure the account isn't locked out. // if ( UserAll->UserId != DOMAIN_USER_RID_ADMIN && (UserAll->UserAccountControl & USER_ACCOUNT_AUTO_LOCKED) ) { // // Since the UI strongly encourages admins to disable user // accounts rather than delete them. Treat disabled acccount as // non-authoritative allowing the search to continue for other // accounts by the same name. // if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) { authoritative = FALSE; } status = STATUS_ACCOUNT_LOCKED_OUT; goto CleanUp; } // // Get the encrypted password from the user parms field // status = NetpParmsQueryUserPropertyWithLength( &UserAll->Parameters, NWPASSWORD, &PropertyFlag, &EncryptedPassword ); if ( !NT_SUCCESS( status )) { goto CleanUp; } // // If the user does not have a netware password, fail the login // if ( EncryptedPassword.Length == 0 ) { status = STATUS_NO_SUCH_USER; goto CleanUp; } // // Read our LSA secret if we haven't already // if (! GotSecret) { status = GetNcpSecretKey( &LsaSecretBuffer[0] ); if (! NT_SUCCESS(status)) { goto CleanUp; } GotSecret = TRUE; } // // Decrypt the password with NetwareLsaSecret to get the one way form // status = RtlDecryptNtOwfPwdWithUserKey( (PENCRYPTED_NT_OWF_PASSWORD) EncryptedPassword.Buffer, (PUSER_SESSION_KEY) &LsaSecretBuffer[0], &DecryptedPassword ); if ( !NT_SUCCESS( status )) { goto CleanUp; } // // Get the response to challenge. We do this by finishing off the // password encryption algorithm here. // challenge = (PCHAR) &(LogonNetworkInfo->LmChallenge); Shuffle( challenge, (UCHAR *) &(DecryptedPassword.data), 16, &achK[0] ); Shuffle( challenge+4, (UCHAR *) &(DecryptedPassword.data), 16, &achK[16] ); for (index = 0; index < 16; index++) { achK[index] ^= achK[31-index]; } for (index = 0; index < RESPONSE_SIZE; index++) { Response[index] = achK[index] ^ achK[15-index]; } if ( memcmp( Response, (PCHAR) (LogonNetworkInfo->LmChallengeResponse.Buffer), RESPONSE_SIZE ) != 0 ) { // // Since the UI strongly encourages admins to disable user // accounts rather than delete them. Treat disabled acccount as // non-authoritative allowing the search to continue for other // accounts by the same name. // if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) { authoritative = FALSE; } // // if the user tried to use a NULL password, don't note this as // a bad password attempt since LOGON.EXE does this by default. // Instead, map it to STATUS_LOGON_FAILURE. // { UCHAR pszShuffledNWPassword[NT_OWF_PASSWORD_LENGTH * 2]; DWORD chObjectId; NT_PRODUCT_TYPE ProductType; DWORD dwUserId; // // first we calculate what the user's Object ID is... // RtlGetNtProductType( &ProductType ); dwUserId = MapRidToObjectId( UserAll->UserId, UserAll->UserName.Buffer, ProductType == NtProductLanManNt, FALSE ); chObjectId = SWAP_OBJECT_ID (dwUserId); // // then we calculate the user's password residue with a null // password // RtlZeroMemory( &pszShuffledNWPassword, NT_OWF_PASSWORD_LENGTH * 2 ); Shuffle( (UCHAR *) &chObjectId, NULL, 0, pszShuffledNWPassword ); // // we then finish off the encryption as we did above for the // password in the user's record. // challenge = (PCHAR) &(LogonNetworkInfo->LmChallenge); Shuffle( challenge, pszShuffledNWPassword, 16, &achK[0] ); Shuffle( challenge+4, pszShuffledNWPassword, 16, &achK[16] ); for (index = 0; index < 16; index++) { achK[index] ^= achK[31-index]; } for (index = 0; index < RESPONSE_SIZE; index++) { Response[index] = achK[index] ^ achK[15-index]; } // // now if the password that the user sent in matches the encrypted // form of the null password, we exit with a generic return code // that won't cause the user's record to be updated. This will // also cause LSA to not wait for 3 seconds to return the error // (which is a good thing in this case). // if ( memcmp( Response, (PCHAR) (LogonNetworkInfo->LmChallengeResponse.Buffer), RESPONSE_SIZE ) == 0 ) { status = STATUS_LOGON_FAILURE; } else { status = STATUS_WRONG_PASSWORD; } } goto CleanUp; } // // Prevent rest of things from effecting the Administrator user // if (UserAll->UserId == DOMAIN_USER_RID_ADMIN) { status = STATUS_SUCCESS; goto CleanUp; } // // Check if the account is disabled. // if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED ) { // // Since the UI strongly encourages admins to disable user // accounts rather than delete them. Treat disabled acccount as // non-authoritative allowing the search to continue for other // accounts by the same name. // authoritative = FALSE; status = STATUS_ACCOUNT_DISABLED; goto CleanUp; } // // Check if the account has expired. // if (UserAll->AccountExpires.QuadPart > 0 && LogonTime.QuadPart >= UserAll->AccountExpires.QuadPart ) { status = STATUS_ACCOUNT_EXPIRED; goto CleanUp; } status = QueryDomainPasswordInfo( &DomainInfo ); if ( !NT_SUCCESS( status )) { goto CleanUp; } // // Response is correct. So, check if the password has expired or not // if (! (UserAll->UserAccountControl & USER_DONT_EXPIRE_PASSWORD)) { status = NetpParmsQueryUserPropertyWithLength( &UserAll->Parameters, NWTIMEPASSWORDSET, &PropertyFlag, &PasswordDateSet ); if ( !NT_SUCCESS( status ) || PasswordDateSet.Length < sizeof(LARGE_INTEGER) ) { // date last password was set was not present.... hmmm. // we won't update anything here but let someone know all // is not kosher by making this a grace login. userFlags = LOGON_GRACE_LOGON; } else { pwSetTime = *((PLARGE_INTEGER)(PasswordDateSet.Buffer)); if ( (pwSetTime.HighPart == 0xFFFF && pwSetTime.LowPart == 0xFFFF ) || GetPasswordExpired( pwSetTime, DomainInfo->Password.MaxPasswordAge )) { // // if we're on a bdc, just exit with invalid password, then // we'll go try it on the PDC. // POLICY_LSA_SERVER_ROLE_INFO *LsaServerRole; PLSAPR_POLICY_INFORMATION LsaPolicyBuffer = NULL; status = (*pfLsaIQueryInformationPolicyTrusted)( PolicyLsaServerRoleInformation, &LsaPolicyBuffer ); if ( NT_SUCCESS( status ) && (LsaPolicyBuffer != NULL)) { LsaServerRole = (POLICY_LSA_SERVER_ROLE_INFO *) LsaPolicyBuffer; if (LsaServerRole->LsaServerRole == PolicyServerRoleBackup) { LsaFreeMemory( LsaServerRole ); status = STATUS_PASSWORD_EXPIRED; goto CleanUp; } LsaFreeMemory( LsaServerRole ); } // // Password has expired, so check if grace login is still allowed // userFlags = LOGON_GRACE_LOGON; // // if this is a password validate rather than an // actual login, don't update/check grace logins. // if ( LogonNetworkInfo->Identity.Workstation.Length > 0 ) { status = NetpParmsQueryUserPropertyWithLength( &UserAll->Parameters, GRACELOGINREMAINING, &PropertyFlag, &GraceLoginRemaining ); if ( ! NT_SUCCESS( status ) ) { // // The grace login value cannot be determined. // goto CleanUp; } else if ( ( GraceLoginRemaining.Length != 0 ) && ( *(GraceLoginRemaining.Buffer) > 0 ) ) { // // Password has expired and grace login is available. // So, return success and decrease the grace login remaining // in the user parms field. // BOOL fUpdate; (*(GraceLoginRemaining.Buffer))--; status = NetpParmsSetUserProperty( UserAll->Parameters.Buffer, GRACELOGINREMAINING, GraceLoginRemaining, USER_PROPERTY_TYPE_ITEM, &pNewUserParams, &fUpdate ); if ( NT_SUCCESS( status) && fUpdate ) { // // if we actually updated the parms, mark as such. // whichFields = USER_ALL_PARAMETERS; // // The length of the parameters didn't grow... we // know that because we started with a value and // ended with the same value - 1 ( same length ) // memcpy( UserAll->Parameters.Buffer, pNewUserParams, UserAll->Parameters.Length ); } status = STATUS_SUCCESS; } else { status = STATUS_PASSWORD_EXPIRED; goto CleanUp; } } } } } // // To validate the user's logon hours, we must have a handle to the user. // We'll open the user here. // UserHandle = NULL; status = (*pfSamrOpenUser)( SamDomainHandle, USER_READ_ACCOUNT, UserAll->UserId, &UserHandle ); if ( !NT_SUCCESS(status) ) { KdPrint(( "FPNWCLNT: Cannot SamrOpenUser %lX\n", status)); goto CleanUp; } // // Validate the user's logon hours. // status = (*pfSamIAccountRestrictions)( UserHandle, NULL, // workstation id NULL, // workstation list &UserAll->LogonHours, &logoffTime, &kickoffTime ); (*pfSamrCloseHandle)( &UserHandle ); if ( ! NT_SUCCESS( status )) { goto CleanUp; } // // Validate if the user can logon from this workstation. // (Supply subauthentication package specific code here.) if ( ! MNSWorkstationValidate( &LogonNetworkInfo->Identity.Workstation, &UserAll->Parameters ) ) { status = STATUS_INVALID_WORKSTATION; goto CleanUp; } // // The user is valid. CleanUp up before returning. // CleanUp: // // If we succeeded, create a session key. The session key is created // by taking the decrypted password (a hash of the object id and // cleartext password) and adding the index of each byte to each byte // modulo 255, and using that to create a new challenge response from // the old challenge response. // if (NT_SUCCESS(status) && (UserSessionKey != NULL)) { UCHAR ChallengeResponse[NT_CHALLENGE_LENGTH]; PUCHAR Password = (PUCHAR) &DecryptedPassword.data; PUCHAR SessionKey = (PUCHAR) UserSessionKey; ASSERT(RESPONSE_SIZE >= NT_CHALLENGE_LENGTH); RtlZeroMemory( UserSessionKey, sizeof(*UserSessionKey) ); RtlCopyMemory( ChallengeResponse, Response, NT_CHALLENGE_LENGTH ); // // Create the new password // for (index = 0; index < sizeof(DecryptedPassword) ; index++ ) { Password[index] = Password[index] + (UCHAR) index; } // // Use it to make a normal challenge response using the old challenge // response // Shuffle( ChallengeResponse, (UCHAR *) &(DecryptedPassword.data), 16, &achK[0] ); Shuffle( ChallengeResponse+4, (UCHAR *) &(DecryptedPassword.data), 16, &achK[16] ); for (index = 0; index < 16; index++) { achK[index] ^= achK[31-index]; } for (index = 0; index < RESPONSE_SIZE; index++) { SessionKey[index] = achK[index] ^ achK[15-index]; } userFlags |= LOGON_SUBAUTH_SESSION_KEY; } if (DomainInfo != NULL) { (*pfSamIFree_SAMPR_DOMAIN_INFO_BUFFER)( DomainInfo, DomainPasswordInformation ); } if (EncryptedPassword.Buffer == NULL) { LocalFree( EncryptedPassword.Buffer ); } if (PasswordDateSet.Buffer != NULL) { LocalFree( PasswordDateSet.Buffer ); } if (GraceLoginRemaining.Buffer != NULL) { LocalFree( GraceLoginRemaining.Buffer ); } if (pNewUserParams != NULL) { NetpParmsUserPropertyFree( pNewUserParams ); } *Authoritative = authoritative; *UserFlags = userFlags; *WhichFields = whichFields; LogoffTime->HighPart = logoffTime.HighPart; LogoffTime->LowPart = logoffTime.LowPart; KickoffTime->HighPart = kickoffTime.HighPart; KickoffTime->LowPart = kickoffTime.LowPart; return status; } // Msv1_0SubAuthenticationRoutine NTSTATUS Msv1_0SubAuthenticationRoutine ( IN NETLOGON_LOGON_INFO_CLASS LogonLevel, IN PVOID LogonInformation, IN ULONG Flags, IN PUSER_ALL_INFORMATION UserAll, OUT PULONG WhichFields, OUT PULONG UserFlags, OUT PBOOLEAN Authoritative, OUT PLARGE_INTEGER LogoffTime, OUT PLARGE_INTEGER KickoffTime ) /*++ Routine Description: Compatibility wrapper for Msv1_0SubAuthenticationRoutine2. Arguments: Same as Msv1_0SubAuthenticationRoutine2 Return Value: Same as Msv1_0SubAuthenticationRoutine2 --*/ { return(Msv1_0SubAuthenticationRoutine2( LogonLevel, LogonInformation, Flags, UserAll, WhichFields, UserFlags, Authoritative, LogoffTime, KickoffTime, NULL // session key ) ); } BOOLEAN MNSWorkstationValidate ( IN PUNICODE_STRING Workstation, IN PUNICODE_STRING UserParameters ) { NTSTATUS status; WCHAR PropertyFlag; UNICODE_STRING LogonWorkstations; INT cbRequired; INT cb; LPWSTR pszTmp; if ( Workstation->Length < (NET_ADDRESS_SIZE * sizeof(WCHAR)) ) { // // Zero is used when simply verifying a password. // // We also check that the length is enough so we dont // blow up later. If for some reason a bad string is // supplied, we pass it. This should never happen. Not a // security hole as the user has no control over the string. // return(TRUE); } status = NetpParmsQueryUserPropertyWithLength( UserParameters, NWLOGONFROM, &PropertyFlag, &LogonWorkstations ); if ( !NT_SUCCESS( status) || LogonWorkstations.Length == 0 ) { return TRUE; } cbRequired = (LogonWorkstations.Length + 1) * sizeof(WCHAR); pszTmp = LocalAlloc( LMEM_ZEROINIT, cbRequired); if ( pszTmp == NULL ) { // // Not enough memory to allocate the buffer. Just // let the user logon. // LocalFree( LogonWorkstations.Buffer ); return TRUE; } cb = MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, (const CHAR *) LogonWorkstations.Buffer, LogonWorkstations.Length, pszTmp, cbRequired ); LocalFree( LogonWorkstations.Buffer ); // Don't need it any more if ( cb > 1 ) { USHORT TotalEntries = LogonWorkstations.Length/WKSTA_ADDRESS_SIZE; WCHAR *pszEntry = pszTmp; WCHAR *pszWksta = Workstation->Buffer ; _wcsupr(pszEntry) ; _wcsupr(pszWksta) ; while ( TotalEntries > 0 ) { // // if net # is not wildcard, check for match // if (wcsncmp(L"FFFFFFFF", pszEntry, NET_ADDRESS_SIZE)!=0) { if (wcsncmp(pszWksta, pszEntry, NET_ADDRESS_SIZE)!=0) { // // if no match, goto next entry // pszEntry += WKSTA_ADDRESS_SIZE; TotalEntries--; continue ; } } // // from above, net number passes. check node number. // again, look for wildcard first. // if (wcsncmp(L"FFFFFFFFFFFF", pszEntry+NET_ADDRESS_SIZE, NODE_ADDRESS_SIZE)!=0) { if (wcsncmp(pszEntry+NET_ADDRESS_SIZE, pszWksta+NET_ADDRESS_SIZE, NODE_ADDRESS_SIZE)!=0) { // // if no match, goto next entry // pszEntry += WKSTA_ADDRESS_SIZE; TotalEntries--; continue ; } } // // found a match. return it. // LocalFree( pszTmp ); return TRUE; } } else { // // MultiByteToWideChar failed or empty string (ie. 1 char). // Just let the user logon // LocalFree( pszTmp ); return TRUE; } LocalFree( pszTmp ); return FALSE; } BOOL GetPasswordExpired( IN LARGE_INTEGER PasswordLastSet, IN LARGE_INTEGER MaxPasswordAge ) /*++ Routine Description: This routine returns true if the password is expired, false otherwise. Arguments: PasswordLastSet - Time when the password was last set for this user. MaxPasswordAge - Maximum password age for any password in the domain. Return Value: Returns true if password is expired. False if not expired. --*/ { LARGE_INTEGER PasswordMustChange; NTSTATUS status; BOOLEAN rc; LARGE_INTEGER TimeNow; // // Compute the expiration time as the time the password was // last set plus the maximum age. // if (PasswordLastSet.QuadPart < 0 || MaxPasswordAge.QuadPart > 0 ) { rc = TRUE; // default for invalid times is that it is expired. } else { try { PasswordMustChange.QuadPart = PasswordLastSet.QuadPart - MaxPasswordAge.QuadPart; // // Limit the resultant time to the maximum valid absolute time // if ( PasswordMustChange.QuadPart < 0 ) { rc = FALSE; } else { status = NtQuerySystemTime( &TimeNow ); if (NT_SUCCESS(status)) { if ( TimeNow.QuadPart >= PasswordMustChange.QuadPart ) { rc = TRUE; } else { rc = FALSE; } } else { rc = FALSE; // won't fail if NtQuerySystemTime failed. } } } except(EXCEPTION_EXECUTE_HANDLER) { rc = TRUE; } } return rc; } NTSTATUS QueryDomainPasswordInfo ( PSAMPR_DOMAIN_INFO_BUFFER *DomainInfo ) /*++ This routine opens a handle to sam so that we can get the max password age. --*/ { NTSTATUS status; OBJECT_ATTRIBUTES PolicyObjectAttributes; PLSAPR_POLICY_INFORMATION PolicyAccountDomainInfo = NULL; // // if we don't yet have a domain handle, open domain handle so that // we can query the domain's password expiration time. // status = LoadSamAndLsa() ; if ( !NT_SUCCESS( status )) { return status ; } if (SamDomainHandle == NULL) { // // Determine the DomainName and DomainId of the Account Database // if (LsaPolicyHandle == NULL) { InitializeObjectAttributes( &PolicyObjectAttributes, NULL, // Name 0, // Attributes NULL, // Root NULL ); // Security Descriptor status = (*pfLsaIOpenPolicyTrusted)(&LsaPolicyHandle); if ( !NT_SUCCESS(status) ) { LsaPolicyHandle = NULL; KdPrint(( "FPNWCLNT: Cannot LsaIOpenPolicyTrusted 0x%x\n", status)); goto CleanUp; } } status = (*pfLsarQueryInformationPolicy)( LsaPolicyHandle, PolicyAccountDomainInformation, &PolicyAccountDomainInfo ); if ( !NT_SUCCESS(status) ) { KdPrint(( "FPNWCLNT: Cannot LsarQueryInformationPolicy 0x%x\n", status)); goto CleanUp; } if ( PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid == NULL ) { status = STATUS_NO_SUCH_DOMAIN; KdPrint(( "FPNWCLNT: Domain Sid is null 0x%x\n", status)); goto CleanUp; } // // Open our connection with SAM // if (SamConnectHandle == NULL) { status = (*pfSamIConnect)( NULL, // No server name &SamConnectHandle, SAM_SERVER_CONNECT, (BOOLEAN) TRUE ); // Indicate we are privileged if ( !NT_SUCCESS(status) ) { SamConnectHandle = NULL; KdPrint(( "FPNWCLNT: Cannot SamIConnect 0x%x\n", status)); goto CleanUp; } } // // Open the domain. // status = (*pfSamrOpenDomain)( SamConnectHandle, DOMAIN_READ_OTHER_PARAMETERS, (RPC_SID *) PolicyAccountDomainInfo->PolicyAccountDomainInfo.DomainSid, &SamDomainHandle ); if ( !NT_SUCCESS(status) ) { SamDomainHandle = NULL; KdPrint(( "FPNWCLNT: Cannot SamrOpenDomain 0x%x\n", status)); goto CleanUp; } } status = (*pfSamrQueryInformationDomain)( SamDomainHandle, DomainPasswordInformation, DomainInfo ); if ( !NT_SUCCESS(status) ) { KdPrint(( "FPNWCLNT: Cannot SamrQueryInformationDomain %lX\n", status)); goto CleanUp; } CleanUp: if (PolicyAccountDomainInfo != NULL) { (*pfLsaIFree_LSAPR_POLICY_INFORMATION)( PolicyAccountDomainInformation, PolicyAccountDomainInfo ); } return(status); } // QueryDomainPasswordInfo // logon.c eof.