/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) Microsoft Corp. All rights reserved. // // FILE // // ezlogon.c // // SYNOPSIS // // Defines the IAS wrapper around LsaLogonUser // // MODIFICATION HISTORY // // 08/15/1998 Original version. // 09/09/1998 Fix AV when logon domain doesn't match user domain. // 10/02/1998 NULL out handle when LsaLogonUser fails. // 10/11/1998 Use SubStatus for STATUS_ACCOUNT_RESTRICTION. // 10/22/1998 PIAS_LOGON_HOURS is now a mandatory parameter. // 01/28/1999 Remove LogonDomainName check. // 04/19/1999 Add IASPurgeTicketCache. // /////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include CONST CHAR LOGON_PROCESS_NAME[] = "IAS"; CONST CHAR TOKEN_SOURCE_NAME[TOKEN_SOURCE_LENGTH] = "IAS"; ////////// // Misc. global variables used for logons. ////////// LSA_HANDLE theLogonProcess; // The handle for the logon process. ULONG theMSV1_0_Package; // The MSV1_0 authentication package. ULONG theKerberosPackage; // The Kerberos authentication package. STRING theOriginName; // The origin of the logon requests. TOKEN_SOURCE theSourceContext; // The source context of the logon requests. /////////////////////////////////////////////////////////////////////////////// // // FUNCTION // // IASLogonInitialize // // DESCRIPTION // // Registers the logon process. // /////////////////////////////////////////////////////////////////////////////// DWORD WINAPI IASLogonInitialize( VOID ) { DWORD status; BOOLEAN wasEnabled; LSA_STRING processName, packageName; LSA_OPERATIONAL_MODE opMode; ////////// // Enable SE_TCB_PRIVILEGE. ////////// status = RtlAdjustPrivilege( SE_TCB_PRIVILEGE, TRUE, FALSE, &wasEnabled ); if (!NT_SUCCESS(status)) { goto exit; } ////////// // Register as a logon process. ////////// RtlInitString( &processName, LOGON_PROCESS_NAME ); status = LsaRegisterLogonProcess( &processName, &theLogonProcess, &opMode ); if (!NT_SUCCESS(status)) { goto exit; } ////////// // Lookup the MSV1_0 authentication package. ////////// RtlInitString( &packageName, MSV1_0_PACKAGE_NAME ); status = LsaLookupAuthenticationPackage( theLogonProcess, &packageName, &theMSV1_0_Package ); if (!NT_SUCCESS(status)) { goto deregister; } ////////// // Lookup the Kerberos authentication package. ////////// RtlInitString( &packageName, MICROSOFT_KERBEROS_NAME_A ); status = LsaLookupAuthenticationPackage( theLogonProcess, &packageName, &theKerberosPackage ); if (!NT_SUCCESS(status)) { goto deregister; } ////////// // Initialize the source context. ////////// memcpy(theSourceContext.SourceName, TOKEN_SOURCE_NAME, TOKEN_SOURCE_LENGTH); status = NtAllocateLocallyUniqueId( &theSourceContext.SourceIdentifier ); if (!NT_SUCCESS(status)) { goto deregister; } return NO_ERROR; deregister: LsaDeregisterLogonProcess(theLogonProcess); theLogonProcess = NULL; exit: return RtlNtStatusToDosError(status); } /////////////////////////////////////////////////////////////////////////////// // // FUNCTION // // IASLogonShutdown // // DESCRIPTION // // Deregisters the logon process. // /////////////////////////////////////////////////////////////////////////////// VOID WINAPI IASLogonShutdown( VOID ) { LsaDeregisterLogonProcess(theLogonProcess); theLogonProcess = NULL; } /////////////////////////////////////////////////////////////////////////////// // // FUNCTION // // IASInitAuthInfo // // DESCRIPTION // // Initializes the fields common to all MSV1_0_LM20* structs. // /////////////////////////////////////////////////////////////////////////////// VOID WINAPI IASInitAuthInfo( IN PVOID AuthInfo, IN DWORD FixedLength, IN PCWSTR UserName, IN PCWSTR Domain, OUT PBYTE* Data ) { PMSV1_0_LM20_LOGON logon; // Zero out the fixed data. memset(AuthInfo, 0, FixedLength); // Set Data to point just past the fixed struct. *Data = FixedLength + (PBYTE)AuthInfo; // This cast is safe since all LM20 structs have the same initial fields. logon = (PMSV1_0_LM20_LOGON)AuthInfo; // We always do Network logons. logon->MessageType = MsV1_0NetworkLogon; // Copy in the strings common to all logons. IASInitUnicodeString(logon->LogonDomainName, *Data, Domain); IASInitUnicodeString(logon->UserName, *Data, UserName); IASInitUnicodeString(logon->Workstation, *Data, L""); } /////////////////////////////////////////////////////////////////////////////// // // FUNCTION // // IASLogonUser // // DESCRIPTION // // Wrapper around LsaLogonUser. // /////////////////////////////////////////////////////////////////////////////// DWORD WINAPI IASLogonUser( IN PVOID AuthInfo, IN ULONG AuthInfoLength, OUT PMSV1_0_LM20_LOGON_PROFILE *Profile, OUT PHANDLE Token ) { NTSTATUS status, SubStatus; PMSV1_0_LM20_LOGON_PROFILE ProfileBuffer; ULONG ProfileBufferLength; LUID LogonId; QUOTA_LIMITS Quotas; // Make sure the OUT arguments are NULL. *Token = NULL; ProfileBuffer = NULL; status = LsaLogonUser( theLogonProcess, &theOriginName, Network, theMSV1_0_Package, AuthInfo, AuthInfoLength, NULL, &theSourceContext, &ProfileBuffer, &ProfileBufferLength, &LogonId, Token, &Quotas, &SubStatus ); if (!NT_SUCCESS(status)) { // For account restrictions, we can get a more descriptive error // from the SubStatus. if (status == STATUS_ACCOUNT_RESTRICTION && !NT_SUCCESS(SubStatus)) { status = SubStatus; } // Sometimes LsaLogonUser returns an invalid handle value on failure. *Token = NULL; } if (Profile) { // Return the profile if requested ... *Profile = ProfileBuffer; } else if (ProfileBuffer) { // ... otherwise free it. LsaFreeReturnBuffer(ProfileBuffer); } return RtlNtStatusToDosError(status); } /////////////////////////////////////////////////////////////////////////////// // // FUNCTION // // IASCheckAccountRestrictions // // DESCRIPTION // // Checks whether an account can be used for logon. // /////////////////////////////////////////////////////////////////////////////// DWORD WINAPI IASCheckAccountRestrictions( IN PLARGE_INTEGER AccountExpires, IN PIAS_LOGON_HOURS LogonHours, OUT PLARGE_INTEGER KickOffTime ) { LONGLONG now, logonHoursExpiry; TIME_ZONE_INFORMATION tzi; SYSTEMTIME st; size_t msecOfWeek, msecPerUnit, idx, lastUnit, msecLeft; const size_t msecPerWeek = 1000 * 60 * 60 * 24 * 7; GetSystemTimeAsFileTime((LPFILETIME)&now); if (AccountExpires->QuadPart == 0) { // An expiration time of zero means 'never'. KickOffTime->QuadPart = MAXLONGLONG; } else if (AccountExpires->QuadPart > now) { KickOffTime->QuadPart = AccountExpires->QuadPart; } else { return ERROR_ACCOUNT_EXPIRED; } // If LogonHours is empty, then we're done. if (LogonHours->UnitsPerWeek == 0) { return NO_ERROR; } // The LogonHours array does not account for bias. switch (GetTimeZoneInformation(&tzi)) { case TIME_ZONE_ID_UNKNOWN: case TIME_ZONE_ID_STANDARD: // Bias is in minutes. now -= 60 * 10000000 * (LONGLONG)tzi.StandardBias; break; case TIME_ZONE_ID_DAYLIGHT: // Bias is in minutes. now -= 60 * 10000000 * (LONGLONG)tzi.DaylightBias; break; default: return ERROR_INVALID_LOGON_HOURS; } FileTimeToSystemTime((LPFILETIME)&now, &st); // Number of milliseconds into the week. msecOfWeek = st.wMilliseconds + st.wSecond * 1000 + st.wMinute * 1000 * 60 + st.wHour * 1000 * 60 * 60 + st.wDayOfWeek * 1000 * 60 * 60 * 24; // Compute the index of the current time (our starting point). msecPerUnit = msecPerWeek / LogonHours->UnitsPerWeek; idx = msecOfWeek / msecPerUnit; // Number of units until we hit an unset bit. lastUnit = 0; while (lastUnit < LogonHours->UnitsPerWeek) { // Test the corresponding bit. if ((LogonHours->LogonHours[idx / 8] & (0x1 << (idx % 8))) == 0) { break; } ++lastUnit; ++idx; // Wrap around if necessary. if (idx == LogonHours->UnitsPerWeek) { idx = 0; } } if (lastUnit == LogonHours->UnitsPerWeek) { // All bits are set, so leave the KickOffTime alone. } else if (lastUnit > 0) { // How many milliseconds left? msecLeft = (lastUnit - 1) * msecPerUnit; msecLeft += msecPerUnit - (msecOfWeek % msecPerUnit); // Add this to the current time to find out when logon hours expires. logonHoursExpiry = now + (msecLeft * 10000i64); // Is this more restrictive than the the current KickOffTime? if (logonHoursExpiry < KickOffTime->QuadPart) { KickOffTime->QuadPart = logonHoursExpiry; } } else { // Current bit isn't set. return ERROR_INVALID_LOGON_HOURS; } return NO_ERROR; } /////////////////////////////////////////////////////////////////////////////// // // FUNCTION // // IASPurgeTicketCache // // DESCRIPTION // // Purges the Kerberos ticket cache. // /////////////////////////////////////////////////////////////////////////////// DWORD WINAPI IASPurgeTicketCache( VOID ) { KERB_PURGE_TKT_CACHE_REQUEST request; NTSTATUS status, subStatus; PVOID response; ULONG responseLength; memset(&request, 0, sizeof(request)); request.MessageType = KerbPurgeTicketCacheMessage; response = NULL; responseLength = 0; subStatus = 0; status = LsaCallAuthenticationPackage( theLogonProcess, theKerberosPackage, &request, sizeof(request), &response, &responseLength, &subStatus ); if (NT_SUCCESS(status) && (response != NULL)) { LsaFreeReturnBuffer(response); } return RtlNtStatusToDosError(status); }