//+----------------------------------------------------------------------- // // Microsoft Windows // // Copyright (c) Microsoft Corporation 1992 - 1996 // // File: kerbutil.cxx // // Contents: Utility functions for Kerberos package // // // History: 16-April-1996 Created MikeSw // //------------------------------------------------------------------------ #include #include #ifndef WIN32_CHICAGO #include #else // WIN32_CHICAGO #define NCBNAMSZ 16 #endif // WIN32_CHICAGO #include // for gss support routines #ifdef RETAIL_LOG_SUPPORT static TCHAR THIS_FILE[]=TEXT(__FILE__); #endif GUID GUID_NULL = {0L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; //+------------------------------------------------------------------------- // // Function: KerbSplitFullServiceName // // Synopsis: Splits a full service name into a domain name and a // service name. The output strings point into the input // string's buffer and should not be freed // // Effects: // // Arguments: FullServiceName - The full service name in a domain\service // format // DomainName - Receives the domain portion of the full service // name in a 'domain' format // ServiceName - Receives the service name in a 'service' format // // Requires: // // Returns: STATUS_INVALID_PARAMETER if the service name does not // match the correct format. // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbSplitFullServiceName( IN PUNICODE_STRING FullServiceName, OUT PUNICODE_STRING DomainName, OUT PUNICODE_STRING ServiceName ) { UNICODE_STRING TempDomainName; UNICODE_STRING TempServiceName; TempDomainName = *FullServiceName; // // Find the split between domain and service name // TempDomainName.Length = 0; while ((TempDomainName.Length < FullServiceName->Length) && (TempDomainName.Buffer[TempDomainName.Length/sizeof(WCHAR)] != L'\\') && (TempDomainName.Buffer[TempDomainName.Length/sizeof(WCHAR)] != L'@') ) { TempDomainName.Length += sizeof(WCHAR); } // // In this case, there is no separator // if (TempDomainName.Length == FullServiceName->Length) { *ServiceName = *FullServiceName; EMPTY_UNICODE_STRING( DomainName ); return(STATUS_SUCCESS); } // // If the separator is an "@" switch the doman & service portion // if (TempDomainName.Buffer[TempDomainName.Length/sizeof(WCHAR)] == L'@') { TempServiceName = TempDomainName; TempDomainName.Buffer = TempServiceName.Buffer + TempServiceName.Length/sizeof(WCHAR) + 1; TempServiceName.MaximumLength = TempServiceName.Length; // // The Domain name is everything else // TempDomainName.Length = FullServiceName->Length - TempServiceName.Length - sizeof(WCHAR); TempDomainName.MaximumLength = TempDomainName.Length; } else { TempServiceName.Buffer = TempDomainName.Buffer + TempDomainName.Length/sizeof(WCHAR) + 1; TempDomainName.MaximumLength = TempDomainName.Length; // // The service name is everything else // TempServiceName.Length = FullServiceName->Length - TempDomainName.Length - sizeof(WCHAR); TempServiceName.MaximumLength = TempServiceName.Length; } // // We could be pointing at the end of the buffer. Set this to NULL // if (TempServiceName.Length == 0) { TempServiceName.Buffer = NULL; } if (TempDomainName.Length == 0) { TempDomainName.Buffer = NULL; } *ServiceName = TempServiceName; *DomainName = TempDomainName; return(STATUS_SUCCESS); } //+------------------------------------------------------------------------- // // Function: KerbAllocateNonce // // Synopsis: Allocates a locally unique number // // Effects: // // Arguments: none // // Requires: // // Returns: the nonce // // Notes: // // //-------------------------------------------------------------------------- ULONG KerbAllocateNonce( VOID ) { LUID TempLuid; TimeStamp CurrentTime; NtAllocateLocallyUniqueId(&TempLuid); GetSystemTimeAsFileTime((PFILETIME) &CurrentTime); #ifndef WIN32_CHICAGO return(0x7fffffff & (TempLuid.LowPart ^ TempLuid.HighPart ^ CurrentTime.LowPart ^ CurrentTime.HighPart)); #else // WIN32_CHICAGO return(0x7fffffff & ((ULONG)(TempLuid.LowPart ^ TempLuid.HighPart ^ CurrentTime))); #endif // WIN32_CHICAGO } //+------------------------------------------------------------------------- // // Function: KerbAllocate // // Synopsis: Allocate memory in either lsa mode or user mode // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- PVOID KerbAllocate( IN SIZE_T BufferSize ) { PVOID pBuffer = NULL; if (KerberosState == KerberosLsaMode) { pBuffer = LsaFunctions->AllocateLsaHeap((ULONG) BufferSize); // Lsa helper routine zeroes the memory. } else { DsysAssert(KerberosState == KerberosUserMode); pBuffer = LocalAlloc(0,BufferSize); if (pBuffer) { RtlZeroMemory (pBuffer, BufferSize); } } return pBuffer; } //+------------------------------------------------------------------------- // // Function: KerbFree // // Synopsis: Free memory in either lsa mode or user mode // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- VOID KerbFree( IN PVOID Buffer ) { if (ARGUMENT_PRESENT(Buffer)) { if (KerberosState == KerberosLsaMode) { LsaFunctions->FreeLsaHeap(Buffer); } else { DsysAssert(KerberosState == KerberosUserMode); LocalFree(Buffer); } } } //+------------------------------------------------------------------------- // // Function: KerbStringToUnicodeString() // // Synopsis: Takes a ansi string and (1) unicodes it, (2) copies it // // Effects: // // Arguments: pDest must be initialized unicode string // // Requires: // // Returns: Free .buffer using KerbFree() // // Notes: // //-------------------------------------------------------------------------- BOOLEAN KerbMbStringToUnicodeString(PUNICODE_STRING pDest, char * pszString) { int cbNewString = 0; USHORT cbOriginalString; BOOLEAN fRet = FALSE; cbOriginalString = (USHORT) strlen(pszString) + 1; cbNewString = MultiByteToWideChar( CP_OEMCP, MB_PRECOMPOSED, pszString, cbOriginalString, NULL, 0 ); if ( cbNewString && ( cbNewString < KERB_MAX_UNICODE_STRING )) { pDest->Buffer = (PWSTR) KerbAllocate(cbNewString); if (NULL == pDest->Buffer) { return FALSE; } if (MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, pszString, cbOriginalString, pDest->Buffer, cbNewString)) { pDest->Length = (USHORT) cbNewString; pDest->MaximumLength = (USHORT) cbNewString; fRet = TRUE; } } return fRet; } #ifndef WIN32_CHICAGO //+------------------------------------------------------------------------- // // Function: KerbWaitForEvent // // Synopsis: Wait up to Timeout seconds for EventName to be triggered. // // Effects: // // Arguments: EventName - Name of event to wait on // Timeout - Timeout for event (in seconds). // // Requires: // // Returns: STATUS_SUCCESS - Indicates Event was set. // STATUS_NETLOGON_NOT_STARTED - Timeout occurred. // // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbWaitForEvent( IN LPWSTR EventName, IN ULONG Timeout ) { NTSTATUS Status; HANDLE EventHandle; OBJECT_ATTRIBUTES EventAttributes; UNICODE_STRING EventNameString; LARGE_INTEGER LocalTimeout; // // Create an event for us to wait on. // RtlInitUnicodeString( &EventNameString, EventName); InitializeObjectAttributes( &EventAttributes, &EventNameString, 0, 0, NULL); Status = NtCreateEvent( &EventHandle, SYNCHRONIZE, &EventAttributes, NotificationEvent, (BOOLEAN) FALSE // The event is initially not signaled ); if ( !NT_SUCCESS(Status)) { // // If the event already exists, the server beat us to creating it. // Just open it. // if( Status == STATUS_OBJECT_NAME_EXISTS || Status == STATUS_OBJECT_NAME_COLLISION ) { Status = NtOpenEvent( &EventHandle, SYNCHRONIZE, &EventAttributes ); } if ( !NT_SUCCESS(Status)) { KdPrint(("[MSV1_0] OpenEvent failed %lx\n", Status )); return Status; } } // // Wait for NETLOGON to initialize. Wait a maximum of Timeout seconds. // LocalTimeout.QuadPart = ((LONGLONG)(Timeout)) * (-10000000); Status = NtWaitForSingleObject( EventHandle, (BOOLEAN)FALSE, &LocalTimeout); (VOID) NtClose( EventHandle ); if ( !NT_SUCCESS(Status) || Status == STATUS_TIMEOUT ) { if ( Status == STATUS_TIMEOUT ) { Status = STATUS_NETLOGON_NOT_STARTED; // Map to an error condition } return Status; } return STATUS_SUCCESS; } //+------------------------------------------------------------------------- // // Function: KerbWaitForKdc // // Synopsis: Wait up to Timeout seconds for the netlogon service to start. // // Effects: // // Arguments: Timeout - Timeout for netlogon (in seconds). // // Requires: // // Returns: STATUS_SUCCESS - Indicates NETLOGON successfully initialized. // STATUS_NETLOGON_NOT_STARTED - Timeout occurred. // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbWaitForKdc( IN ULONG Timeout ) { NTSTATUS Status; ULONG NetStatus; SC_HANDLE ScManagerHandle = NULL; SC_HANDLE ServiceHandle = NULL; SERVICE_STATUS ServiceStatus; LPQUERY_SERVICE_CONFIG ServiceConfig; LPQUERY_SERVICE_CONFIG AllocServiceConfig = NULL; QUERY_SERVICE_CONFIG DummyServiceConfig; DWORD ServiceConfigSize; BOOLEAN AutoStart = FALSE; // // If the KDC service is currently running, // skip the rest of the tests. // Status = KerbWaitForEvent( KDC_START_EVENT, 0 ); if ( NT_SUCCESS(Status) ) { KerbKdcStarted = TRUE; return Status; } // // Open a handle to the KDC Service. // ScManagerHandle = OpenSCManager( NULL, NULL, SC_MANAGER_CONNECT ); if (ScManagerHandle == NULL) { DebugLog((DEB_ERROR, " KerbWaitForKdc: OpenSCManager failed: " "%lu. %ws, line %d\n", GetLastError(), THIS_FILE, __LINE__)); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } ServiceHandle = OpenService( ScManagerHandle, SERVICE_KDC, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG ); if ( ServiceHandle == NULL ) { D_DebugLog((DEB_ERROR, "KerbWaitForKdc: OpenService failed: " "%lu. %ws, line %d\n", GetLastError(), THIS_FILE, __LINE__)); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } // // If the KDC service isn't configured to be automatically started // by the service controller, don't bother waiting for it to start - // just check to see if it is started. // // ?? Pass "DummyServiceConfig" and "sizeof(..)" since QueryService config // won't allow a null pointer, yet. if ( QueryServiceConfig( ServiceHandle, &DummyServiceConfig, sizeof(DummyServiceConfig), &ServiceConfigSize )) { ServiceConfig = &DummyServiceConfig; } else { NetStatus = GetLastError(); if ( NetStatus != ERROR_INSUFFICIENT_BUFFER ) { D_DebugLog((DEB_ERROR,"KerbWaitForKdc: QueryServiceConfig failed: " "%lu. %ws, line %d\n", NetStatus, THIS_FILE, __LINE__)); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } SafeAllocaAllocate(AllocServiceConfig, ServiceConfigSize); ServiceConfig = AllocServiceConfig; if ( AllocServiceConfig == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } if ( !QueryServiceConfig( ServiceHandle, ServiceConfig, ServiceConfigSize, &ServiceConfigSize )) { D_DebugLog((DEB_ERROR, "KerbWaitForKdc: QueryServiceConfig " "failed again: %lu. %ws, line %d\n", GetLastError(), THIS_FILE, __LINE__)); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } } if ( ServiceConfig->dwStartType == SERVICE_AUTO_START ) { AutoStart = TRUE; } // // Loop waiting for the KDC service to start. // for (;;) { // // Query the status of the KDC service. // if (! QueryServiceStatus( ServiceHandle, &ServiceStatus )) { D_DebugLog((DEB_ERROR, "KerbWaitForKdc: QueryServiceStatus failed: " "%lu. %ws, line %d\n", GetLastError(), THIS_FILE, __LINE__ )); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } // // Return or continue waiting depending on the state of // the KDC service. // switch( ServiceStatus.dwCurrentState) { case SERVICE_RUNNING: Status = STATUS_SUCCESS; goto Cleanup; case SERVICE_STOPPED: // // If KDC failed to start, // error out now. The caller has waited long enough to start. // if ( ServiceStatus.dwWin32ExitCode != ERROR_SERVICE_NEVER_STARTED ){ #if DBG D_DebugLog((DEB_ERROR, "KerbWaitForKdc: " "KDC service couldn't start: %lu %lx. %ws, line %d\n", ServiceStatus.dwWin32ExitCode, ServiceStatus.dwWin32ExitCode, THIS_FILE, __LINE__ )); if ( ServiceStatus.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR ) { D_DebugLog((DEB_ERROR, " Service specific error code: %lu %lx. %ws, line %d\n", ServiceStatus.dwServiceSpecificExitCode, ServiceStatus.dwServiceSpecificExitCode, THIS_FILE, __LINE__ )); } #endif // DBG Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } // // If KDC has never been started on this boot, // continue waiting for it to start. // break; // // If KDC is trying to start up now, // continue waiting for it to start. // case SERVICE_START_PENDING: break; // // Any other state is bogus. // default: D_DebugLog((DEB_ERROR, "KerbWaitForKdc: " "Invalid service state: %lu. %ws, line %d\n", ServiceStatus.dwCurrentState, THIS_FILE, __LINE__ )); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } // // If the service wasn't auto start, don't bother waiting and // retrying // if ((ServiceStatus.dwCurrentState) != SERVICE_START_PENDING && !AutoStart) { break; } // // Wait a second for the KDC service to start. // If it has successfully started, just return now. // Status = KerbWaitForEvent( KDC_START_EVENT, 1 ); if ( Status != STATUS_NETLOGON_NOT_STARTED ) { goto Cleanup; } // // If we've waited long enough for KDC to start, // time out now. // if ( (--Timeout) == 0 ) { Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } } /* NOT REACHED */ Cleanup: if ( ScManagerHandle != NULL ) { (VOID) CloseServiceHandle(ScManagerHandle); } if ( ServiceHandle != NULL ) { (VOID) CloseServiceHandle(ServiceHandle); } SafeAllocaFree( AllocServiceConfig ); if (NT_SUCCESS(Status)) { KerbKdcStarted = TRUE; } else { KerbKdcStarted = FALSE; } return Status; } //+------------------------------------------------------------------------- // // Function: KerbWaitForService // // Synopsis: Wait up to Timeout seconds for the service to start. // // Effects: // // Arguments: ServiceName - Name of service to wait for // ServiceEvent - Optionally has event name signalling that // service is started // Timeout - Timeout for netlogon (in seconds). // // Requires: // // Returns: STATUS_SUCCESS - Indicates NETLOGON successfully initialized. // STATUS_NETLOGON_NOT_STARTED - Timeout occurred. // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbWaitForService( IN LPWSTR ServiceName, IN OPTIONAL LPWSTR ServiceEvent, IN ULONG Timeout ) { NTSTATUS Status = STATUS_SUCCESS; ULONG NetStatus; SC_HANDLE ScManagerHandle = NULL; SC_HANDLE ServiceHandle = NULL; SERVICE_STATUS ServiceStatus; LPQUERY_SERVICE_CONFIG ServiceConfig; LPQUERY_SERVICE_CONFIG AllocServiceConfig = NULL; QUERY_SERVICE_CONFIG DummyServiceConfig; DWORD ServiceConfigSize; BOOLEAN AutoStart = FALSE; if (ARGUMENT_PRESENT(ServiceEvent)) { // // If the KDC service is currently running, // skip the rest of the tests. // Status = KerbWaitForEvent( ServiceEvent, 0 ); if ( NT_SUCCESS(Status) ) { return Status; } } // // Open a handle to the Service. // ScManagerHandle = OpenSCManager( NULL, NULL, SC_MANAGER_CONNECT ); if (ScManagerHandle == NULL) { D_DebugLog((DEB_ERROR, " KerbWaitForService: OpenSCManager failed: " "%lu. %ws, line %d\n", GetLastError(), THIS_FILE, __LINE__)); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } ServiceHandle = OpenService( ScManagerHandle, ServiceName, SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG ); if ( ServiceHandle == NULL ) { D_DebugLog((DEB_ERROR, "KerbWaitForService: OpenService failed: " "%lu. %ws, line %d\n", GetLastError(), THIS_FILE, __LINE__)); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } // // If the KDC service isn't configured to be automatically started // by the service controller, don't bother waiting for it to start - // just check to see if it is started. // // ?? Pass "DummyServiceConfig" and "sizeof(..)" since QueryService config // won't allow a null pointer, yet. if ( QueryServiceConfig( ServiceHandle, &DummyServiceConfig, sizeof(DummyServiceConfig), &ServiceConfigSize )) { ServiceConfig = &DummyServiceConfig; } else { NetStatus = GetLastError(); if ( NetStatus != ERROR_INSUFFICIENT_BUFFER ) { D_DebugLog((DEB_ERROR,"KerbWaitForService: QueryServiceConfig failed: " "%lu. %ws, line %d\n", NetStatus, THIS_FILE, __LINE__)); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } SafeAllocaAllocate(AllocServiceConfig, ServiceConfigSize); ServiceConfig = AllocServiceConfig; if ( AllocServiceConfig == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } if ( !QueryServiceConfig( ServiceHandle, ServiceConfig, ServiceConfigSize, &ServiceConfigSize )) { D_DebugLog((DEB_ERROR, "KerbWaitForService: QueryServiceConfig " "failed again: %lu. %ws, line %d\n", GetLastError(), THIS_FILE, __LINE__)); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } } if ( ServiceConfig->dwStartType == SERVICE_AUTO_START ) { AutoStart = TRUE; } // // Loop waiting for the KDC service to start. // for (;;) { // // Query the status of the KDC service. // if (! QueryServiceStatus( ServiceHandle, &ServiceStatus )) { D_DebugLog((DEB_ERROR, "KerbWaitForService: QueryServiceStatus failed: " "%lu. %ws, line %d\n", GetLastError(), THIS_FILE, __LINE__ )); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } // // Return or continue waiting depending on the state of // the KDC service. // switch( ServiceStatus.dwCurrentState) { case SERVICE_RUNNING: Status = STATUS_SUCCESS; goto Cleanup; case SERVICE_STOPPED: // // If KDC failed to start, // error out now. The caller has waited long enough to start. // if ( ServiceStatus.dwWin32ExitCode != ERROR_SERVICE_NEVER_STARTED ){ #if DBG D_DebugLog((DEB_ERROR, "KerbWaitForService: " "%ws service couldn't start: %lu %lx. %ws, line %d\n", ServiceName, ServiceStatus.dwWin32ExitCode, ServiceStatus.dwWin32ExitCode, THIS_FILE, __LINE__ )); if ( ServiceStatus.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR ) { D_DebugLog((DEB_ERROR, " Service specific error code: %lu %lx. %ws, line %d\n", ServiceStatus.dwServiceSpecificExitCode, ServiceStatus.dwServiceSpecificExitCode, THIS_FILE, __LINE__ )); } #endif // DBG Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } // // If service has never been started on this boot, // continue waiting for it to start. // break; // // If service is trying to start up now, // continue waiting for it to start. // case SERVICE_START_PENDING: break; // // Any other state is bogus. // default: D_DebugLog((DEB_ERROR, "KerbWaitForService: " "Invalid service state: %lu. %ws, line %d\n", ServiceStatus.dwCurrentState, THIS_FILE, __LINE__ )); Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } // // If the service wasn't auto start, don't bother waiting and // retrying // if (!AutoStart) { break; } // // Wait a second for the KDC service to start. // If it has successfully started, just return now. // if (ARGUMENT_PRESENT(ServiceEvent)) { Status = KerbWaitForEvent( ServiceEvent, 1 ); if ( Status != STATUS_NETLOGON_NOT_STARTED ) { goto Cleanup; } } else { Sleep(1000); } // // If we've waited long enough for KDC to start, // time out now. // if ( (--Timeout) == 0 ) { Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } } /* NOT REACHED */ Cleanup: if ( ScManagerHandle != NULL ) { (VOID) CloseServiceHandle(ScManagerHandle); } if ( ServiceHandle != NULL ) { (VOID) CloseServiceHandle(ServiceHandle); } SafeAllocaFree(AllocServiceConfig); return Status; } #endif // WIN32_CHICAGO //+------------------------------------------------------------------------- // // Function: KerbMapContextFlags // // Synopsis: Maps the ISC_RET_xx flags to ASC_RET_xxx flags // // Effects: // // Arguments: ContextFlags - Flags to map // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- struct _KERB_FLAG_MAPPING { ULONG InitFlag; ULONG AcceptFlag; } KerbContextFlagMappingTable[] = { {ISC_RET_EXTENDED_ERROR, ASC_RET_EXTENDED_ERROR}, {ISC_RET_INTEGRITY , ASC_RET_INTEGRITY }, {ISC_RET_IDENTIFY, ASC_RET_IDENTIFY }, {ISC_RET_NULL_SESSION, ASC_RET_NULL_SESSION } }; #define KERB_CONTEXT_FLAG_IDENTICAL 0xFFF & ~( ISC_RET_USED_COLLECTED_CREDS | ISC_RET_USED_SUPPLIED_CREDS) ULONG KerbMapContextFlags( IN ULONG ContextFlags ) { ULONG OutputFlags; ULONG Index; // // First copy the identical flags // OutputFlags = ContextFlags & KERB_CONTEXT_FLAG_IDENTICAL; for (Index = 0; Index < sizeof(KerbContextFlagMappingTable) / (2 * sizeof(ULONG)) ;Index++ ) { if ((ContextFlags & KerbContextFlagMappingTable[Index].InitFlag) != 0) { OutputFlags |= KerbContextFlagMappingTable[Index].AcceptFlag; } } return(OutputFlags); } //+------------------------------------------------------------------------- // // Function: KerbIsIpAddress // // Synopsis: Checks to see if a target name is an IP address // // Effects: none // // Arguments: TargetName - Name to check // // Requires: // // Returns: TRUE if the name is an ip address // // Notes: IP address consist of only digits and periods, possibly // with a terminating '$'. // // //-------------------------------------------------------------------------- BOOLEAN KerbIsIpAddress( IN PUNICODE_STRING TargetName ) { ULONG Index; ULONG PeriodCount = 0; // // Null names are not IP addresses. // if (!ARGUMENT_PRESENT(TargetName) || (TargetName->Length == 0)) { return(FALSE); } for (Index = 0; Index < TargetName->Length/sizeof(WCHAR) ; Index++ ) { switch(TargetName->Buffer[Index]) { case L'0': case L'1': case L'2': case L'3': case L'4': case L'5': case L'6': case L'7': case L'8': case L'9': continue; case L'$': // // Only allow this at the end. // if (Index != (TargetName->Length/sizeof(WCHAR) -1) ) { return(FALSE); } continue; case L'.': PeriodCount++; break; default: return(FALSE); } } // // We require a period in the name, so return the FoundPeriod flag // if (PeriodCount == 3) { return(TRUE); } else { return(FALSE); } } //+------------------------------------------------------------------------- // // Function: KerbHidePassword // // Synopsis: obscures a password in memory // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- VOID KerbHidePassword( IN OUT PUNICODE_STRING Password ) { LsaFunctions->LsaProtectMemory( Password->Buffer, (ULONG)Password->MaximumLength ); } //+------------------------------------------------------------------------- // // Function: KerbRevealPassword // // Synopsis: Reveals a password that has been hidden // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- VOID KerbRevealPassword( IN OUT PUNICODE_STRING HiddenPassword ) { LsaFunctions->LsaUnprotectMemory( HiddenPassword->Buffer, (ULONG)HiddenPassword->MaximumLength ); } //+------------------------------------------------------------------------- // // Function: KerbDuplicatePassword // // Synopsis: Duplicates a UNICODE_STRING. If the source string buffer is // NULL the destionation will be too. The MaximumLength contains // room for encryption padding data. // // Effects: allocates memory with LsaFunctions.AllocateLsaHeap // // Arguments: DestinationString - Receives a copy of the source string // SourceString - String to copy // // Requires: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbDuplicatePassword( OUT PUNICODE_STRING DestinationString, IN OPTIONAL PUNICODE_STRING SourceString ) { NTSTATUS Status = STATUS_SUCCESS; DestinationString->Buffer = NULL; DestinationString->Length = DestinationString->MaximumLength = 0; if ((ARGUMENT_PRESENT(SourceString)) && (SourceString->Buffer != NULL)) { USHORT PaddingLength; PaddingLength = RTL_ENCRYPT_MEMORY_SIZE - (SourceString->Length % RTL_ENCRYPT_MEMORY_SIZE); if( PaddingLength == RTL_ENCRYPT_MEMORY_SIZE ) { PaddingLength = 0; } // // Make sure we don't overflow maximum length w/ pwd + max padding // pwd won't be 65k long :) // if (SourceString->Length > (KERB_MAX_UNICODE_STRING - RTL_ENCRYPT_MEMORY_SIZE)) { return STATUS_ILL_FORMED_PASSWORD; } DestinationString->Buffer = (LPWSTR) MIDL_user_allocate( SourceString->Length + PaddingLength ); if (DestinationString->Buffer != NULL) { DestinationString->Length = SourceString->Length; DestinationString->MaximumLength = SourceString->Length + PaddingLength; if( DestinationString->MaximumLength == SourceString->MaximumLength ) { // // duplicating an already padded buffer -- pickup the original // pad. // RtlCopyMemory( DestinationString->Buffer, SourceString->Buffer, SourceString->MaximumLength ); } else { // // duplicating an unpadded buffer -- pickup only the string // and fill the rest with pad. // RtlCopyMemory( DestinationString->Buffer, SourceString->Buffer, SourceString->Length ); } } else { Status = STATUS_NO_MEMORY; } } return Status; } #ifdef notdef // use this if we ever need to map errors in kerb to something else. //+------------------------------------------------------------------------- // // Function: KerbMapKerbNtStatusToNtStatus // // Synopsis: Maps an NT status code to a security status // Here's the package's chance to send back generic NtStatus // errors // // Effects: // // Arguments: // // Requires: // // Returns: // // // //-------------------------------------------------------------------------- NTSTATUS KerbMapKerbNtStatusToNtStatus( IN NTSTATUS Status ) { return(Status); } #endif void * __cdecl operator new( size_t nSize ) { return((LPVOID)LocalAlloc(LPTR, nSize)); } void __cdecl operator delete( void *pv ) { LocalFree((HLOCAL)pv); } //+------------------------------------------------------------------------- // // Function: KerbExtractDomainName // // Synopsis: Extracts the domain name from a principal name // // Effects: Allocates the destination string // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbExtractDomainName( OUT PUNICODE_STRING DomainName, IN PKERB_INTERNAL_NAME PrincipalName, IN PUNICODE_STRING TicketSourceDomain ) { NTSTATUS Status = STATUS_SUCCESS; UNICODE_STRING TempPrincipal; UNICODE_STRING TempDomain = NULL_UNICODE_STRING; EMPTY_UNICODE_STRING( DomainName ); // // We do different things depending on the name type: // - for NT_MS_PRINCIPAL we call KerbSplitFullServiceName, then do the // same as for other name types // - For all other names, if the first portion is "krbtgt" then // we use the second portion of the name, otherwise the // TicketSourceRealm // if (PrincipalName->NameType == KRB_NT_MS_PRINCIPAL) { if (PrincipalName->NameCount != 1) { D_DebugLog((DEB_ERROR,"Principal name has more than one name. %ws, line %d\n", THIS_FILE, __LINE__ )); Status = STATUS_TOO_MANY_PRINCIPALS; return(Status); } else { Status = KerbSplitFullServiceName( &PrincipalName->Names[0], &TempDomain, &TempPrincipal ); if (!NT_SUCCESS(Status)) { return(Status); } } } else { // // The principal name is the first portion. If there are exactly // two portions, the domain name is the second portion // TempPrincipal = PrincipalName->Names[0]; if (PrincipalName->NameCount == 2) { TempDomain = PrincipalName->Names[1]; } else { TempDomain = *TicketSourceDomain; } } // // Check to see if the principal is "krbtgt" - if it is, the domain // is TempDomain - otherwise it is TicketSourceDomain. // if (RtlEqualUnicodeString( &TempPrincipal, &KerbGlobalKdcServiceName, TRUE // case insensitive )) { Status = KerbDuplicateString( DomainName, &TempDomain ); } else { Status = KerbDuplicateString( DomainName, TicketSourceDomain ); } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbUtcTimeToLocalTime // // Synopsis: Converts system time (used internally) to local time, which // is returned to callers. // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- VOID KerbUtcTimeToLocalTime( OUT PTimeStamp LocalTime, IN PTimeStamp SystemTime ) { #ifndef WIN32_CHICAGO NTSTATUS Status; Status = RtlSystemTimeToLocalTime( SystemTime, LocalTime ); DsysAssert(NT_SUCCESS(Status)); #else BOOL Result; Result = FileTimeToLocalFileTime( (PFILETIME) SystemTime, (PFILETIME) LocalTime ); DsysAssert(Result); #endif } //+------------------------------------------------------------------------- // // Function: KerbConvertKdcOptionsToTicketFlags // // Synopsis: // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- ULONG KerbConvertKdcOptionsToTicketFlags( IN ULONG KdcOptions ) { ULONG TicketFlags = 0; if ((KdcOptions & KERB_KDC_OPTIONS_forwardable) != 0) { TicketFlags |= KERB_TICKET_FLAGS_forwardable; } if ((KdcOptions & KERB_KDC_OPTIONS_forwarded) != 0) { TicketFlags |= KERB_TICKET_FLAGS_forwarded; } if ((KdcOptions & KERB_KDC_OPTIONS_proxiable) != 0) { TicketFlags |= KERB_TICKET_FLAGS_proxiable; } if ((KdcOptions & KERB_KDC_OPTIONS_proxy) != 0) { TicketFlags |= KERB_TICKET_FLAGS_proxy; } if ((KdcOptions & KERB_KDC_OPTIONS_postdated) != 0) { TicketFlags |= KERB_TICKET_FLAGS_postdated; } if ((KdcOptions & KERB_KDC_OPTIONS_allow_postdate) != 0) { TicketFlags |= KERB_TICKET_FLAGS_may_postdate; } if ((KdcOptions & KERB_KDC_OPTIONS_renewable) != 0) { TicketFlags |= KERB_TICKET_FLAGS_renewable; } return(TicketFlags); } //+------------------------------------------------------------------------- // // Function: KerbGetAddressListFromWinsock // // Synopsis: gets the list of addresses from a winsock ioctl // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbGetAddressListFromWinsock( OUT LPSOCKET_ADDRESS_LIST * SocketAddressList ) { ULONG BytesReturned = 150; LPSOCKET_ADDRESS_LIST AddressList = NULL; INT i,j; ULONG NetStatus; NTSTATUS Status = STATUS_SUCCESS; SOCKET AddressSocket = INVALID_SOCKET; #ifdef WIN32_CHICAGO j = 0; AddressList = (LPSOCKET_ADDRESS_LIST) MIDL_user_allocate(sizeof(SOCKET_ADDRESS_LIST)); if (AddressList == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } #else // WIN32_CHICAGO AddressSocket = WSASocket( AF_INET, SOCK_DGRAM, 0, // PF_INET, NULL, 0, 0 ); if ( AddressSocket == INVALID_SOCKET ) { NetStatus = WSAGetLastError(); D_DebugLog((DEB_ERROR,"WSASocket failed with %ld. %ws, line %d\n", NetStatus, THIS_FILE, __LINE__ )); Status = STATUS_UNSUCCESSFUL; goto Cleanup; } for (;;) { // // Allocate a buffer that should be big enough. // if ( AddressList != NULL ) { MIDL_user_free( AddressList ); } AddressList = (LPSOCKET_ADDRESS_LIST) MIDL_user_allocate( BytesReturned ); if ( AddressList == NULL ) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } // // Get the list of IP addresses // NetStatus = WSAIoctl( AddressSocket, SIO_ADDRESS_LIST_QUERY, NULL, // No input buffer 0, // No input buffer (PVOID) AddressList, BytesReturned, &BytesReturned, NULL, // No overlapped, NULL ); // Not async if ( NetStatus != 0 ) { NetStatus = WSAGetLastError(); // // If the buffer isn't big enough, try again. // if ( NetStatus == WSAEFAULT ) { continue; } D_DebugLog((DEB_ERROR,"KerbGetAddressListFromWinsock: Cannot WSAIoctl SIO_ADDRESS_LIST_QUERY %ld %ld. %ws, line %d\n", NetStatus, BytesReturned, THIS_FILE, __LINE__)); Status = STATUS_UNSUCCESSFUL; goto Cleanup; } break; } // // Weed out any zero IP addresses and other invalid addresses // for ( i = 0, j = 0; i < AddressList->iAddressCount; i++ ) { PSOCKET_ADDRESS SocketAddress; // // Copy this address to the front of the list. // AddressList->Address[j] = AddressList->Address[i]; // // If the address isn't valid, // skip it. // SocketAddress = &AddressList->Address[j]; if ( SocketAddress->iSockaddrLength == 0 || SocketAddress->lpSockaddr == NULL || SocketAddress->lpSockaddr->sa_family != AF_INET || ((PSOCKADDR_IN)(SocketAddress->lpSockaddr))->sin_addr.s_addr == 0 ) { } else { // // Otherwise keep it. // j++; } } #endif // WIN32_CHICAGO AddressList->iAddressCount = j; *SocketAddressList = AddressList; AddressList = NULL; Cleanup: if (AddressList != NULL) { MIDL_user_free(AddressList); } if ( AddressSocket != INVALID_SOCKET ) { closesocket(AddressSocket); } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbBuildHostAddresses // // Synopsis: Builds a list of host addresses to go in a KDC request // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbBuildHostAddresses( IN BOOLEAN IncludeIpAddresses, IN BOOLEAN IncludeNetbiosAddresses, OUT PKERB_HOST_ADDRESSES * HostAddresses ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_HOST_ADDRESSES Addresses = NULL; PKERB_HOST_ADDRESSES TempAddress = NULL; BOOLEAN LockHeld = FALSE; #ifndef WIN32_CHICAGO KerbGlobalReadLock(); LockHeld = TRUE; // // Check to see if we've gotten out addresses from Netlogon yet. // if ( IncludeIpAddresses && KerbGlobalIpAddressCount == 0) { LPSOCKET_ADDRESS_LIST SocketAddressList = NULL; KerbGlobalReleaseLock(); LockHeld = FALSE; // // We haven't get them now // Status = KerbGetAddressListFromWinsock( &SocketAddressList ); if (NT_SUCCESS(Status)) { Status = KerbUpdateGlobalAddresses( SocketAddressList->Address, SocketAddressList->iAddressCount ); MIDL_user_free(SocketAddressList); } else { KerbGlobalWriteLock(); KerbGlobalIpAddressesInitialized = TRUE; KerbGlobalReleaseLock(); } KerbGlobalReadLock(); LockHeld = TRUE; } // // On failure don't bother inserting the IP addresses // if ( Status == STATUS_SUCCESS && IncludeIpAddresses ) { ULONG Index; for (Index = 0; Index < KerbGlobalIpAddressCount ; Index++ ) { TempAddress = (PKERB_HOST_ADDRESSES) KerbAllocate(sizeof(KERB_HOST_ADDRESSES)); if (TempAddress == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } TempAddress->value.address_type = KERB_ADDRTYPE_INET; TempAddress->value.address.length = 4; TempAddress->value.address.value = (PUCHAR) KerbAllocate(4); if (TempAddress->value.address.value == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } RtlCopyMemory ( TempAddress->value.address.value, &KerbGlobalIpAddresses[Index].sin_addr.S_un.S_addr, 4 ); TempAddress->next = Addresses; Addresses = TempAddress; TempAddress = NULL; } } else { Status = STATUS_SUCCESS; } #endif // WIN32_CHICAGO // // Insert the netbios address (if it will fit) // if (IncludeNetbiosAddresses && KerbGlobalKerbMachineName.Length < NCBNAMSZ) { TempAddress = (PKERB_HOST_ADDRESSES) KerbAllocate(sizeof(KERB_HOST_ADDRESSES)); if (TempAddress == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } TempAddress->value.address_type = KERB_ADDRTYPE_NETBIOS; TempAddress->value.address.length = NCBNAMSZ; TempAddress->value.address.value = (PUCHAR) KerbAllocate(NCBNAMSZ); if (TempAddress->value.address.value == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } RtlCopyMemory( TempAddress->value.address.value, KerbGlobalKerbMachineName.Buffer, KerbGlobalKerbMachineName.Length ); memset( TempAddress->value.address.value + KerbGlobalKerbMachineName.Length, ' ', // space NCBNAMSZ - KerbGlobalKerbMachineName.Length ); TempAddress->next = Addresses; Addresses = TempAddress; TempAddress = NULL; } *HostAddresses = Addresses; Addresses = NULL; Cleanup: if (LockHeld) { KerbGlobalReleaseLock(); } if (TempAddress != NULL) { if (TempAddress->value.address.value != NULL) { KerbFree(TempAddress->value.address.value); } KerbFree(TempAddress); } if (Addresses != NULL) { //KerbFreeHostAddresses(Addresses); while (Addresses != NULL) { TempAddress = Addresses; Addresses = Addresses->next; if (TempAddress->value.address.value != NULL) { KerbFree(TempAddress->value.address.value); } KerbFree(TempAddress); } } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbBuildGssErrorMessage // // Synopsis: Builds an error message with GSS framing, if necessary // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbBuildGssErrorMessage( IN KERBERR Error, IN PBYTE ErrorData, IN ULONG ErrorDataSize, IN PKERB_CONTEXT Context, OUT PULONG ErrorMessageSize, OUT PBYTE * ErrorMessage ) { KERBERR KerbErr = KDC_ERR_NONE; NTSTATUS Status = STATUS_SUCCESS; PUNICODE_STRING pDomain = NULL; PBYTE RawErrorMessage = NULL; ULONG RawErrorMessageSize = 0; PBYTE EncodedErrorData = NULL; ULONG EncodedErrorDataSize = 0; PBYTE MessageStart = NULL; KERB_ERROR_METHOD_DATA ApErrorData = {0}; PKERB_INTERNAL_NAME Spn = NULL; gss_OID MechId; // // First, convert the error data to a specified type // if (Error == KRB_AP_ERR_SKEW) { ApErrorData.data_type = KERB_AP_ERR_TYPE_SKEW_RECOVERY; ApErrorData.data_value.value = NULL; ApErrorData.data_value.length = 0; KerbErr = KerbPackData( &ApErrorData, KERB_ERROR_METHOD_DATA_PDU, &EncodedErrorDataSize, &EncodedErrorData ); if (!KERB_SUCCESS(KerbErr)) { Status = KerbMapKerbError(KerbErr); goto Cleanup; } } else if (ErrorDataSize != 0) { if (Error == KRB_AP_ERR_USER_TO_USER_REQUIRED) { EncodedErrorData = ErrorData; EncodedErrorDataSize = ErrorDataSize; } else { ApErrorData.data_type = KERB_AP_ERR_TYPE_NTSTATUS; ApErrorData.data_value.value = ErrorData; ApErrorData.data_value.length = ErrorDataSize; ApErrorData.bit_mask |= data_value_present; KerbErr = KerbPackData( &ApErrorData, KERB_ERROR_METHOD_DATA_PDU, &EncodedErrorDataSize, &EncodedErrorData ); if (!KERB_SUCCESS(KerbErr)) { Status = KerbMapKerbError(KerbErr); goto Cleanup; } } } // // First build the error message // KerbGlobalReadLock(); if (Context->ServerPrincipalName.Buffer != NULL) { KerbErr = KerbConvertStringToKdcName( &Spn, &Context->ServerPrincipalName ); if (!KERB_SUCCESS(KerbErr)) { Status = KerbMapKerbError(KerbErr); goto Cleanup; } } else { Spn = KerbGlobalMitMachineServiceName; } if (KerbGlobalDnsDomainName.Buffer != NULL) { pDomain = &KerbGlobalDnsDomainName; } else if (KerbGlobalDomainName.Buffer != NULL) { pDomain = &KerbGlobalDomainName; } KerbErr = KerbBuildErrorMessageEx( Error, NULL, // no extended error pDomain, Spn, NULL, // no client realm EncodedErrorData, EncodedErrorDataSize, &RawErrorMessageSize, &RawErrorMessage ); KerbGlobalReleaseLock(); if (!KERB_SUCCESS(KerbErr)) { Status = KerbMapKerbError(KerbErr); goto Cleanup; } // // Figure out what OID to use // KerbReadLockContexts(); // // For DCE style we don't use an OID // if ((Context->ContextFlags & ISC_RET_USED_DCE_STYLE) != 0) { KerbUnlockContexts(); *ErrorMessage = RawErrorMessage; *ErrorMessageSize = RawErrorMessageSize; RawErrorMessage = NULL; goto Cleanup; } if ((Context->ContextAttributes & KERB_CONTEXT_USER_TO_USER) != 0) { MechId = gss_mech_krb5_u2u; } else { MechId = gss_mech_krb5_new; } KerbUnlockContexts(); *ErrorMessageSize = g_token_size(MechId, RawErrorMessageSize); *ErrorMessage = (PBYTE) KerbAllocate(*ErrorMessageSize); if (*ErrorMessage == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } // // the g_make_token_header will reset this to point to the end of the // header // MessageStart = *ErrorMessage; g_make_token_header( MechId, RawErrorMessageSize, &MessageStart, KG_TOK_CTX_ERROR ); RtlCopyMemory( MessageStart, RawErrorMessage, RawErrorMessageSize ); Cleanup: if (RawErrorMessage != NULL) { MIDL_user_free(RawErrorMessage); } if (EncodedErrorData != ErrorData) { MIDL_user_free(EncodedErrorData); } if (Spn != NULL && Context->ServerPrincipalName.Buffer != NULL) { MIDL_user_free(Spn); } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbReceiveErrorMessage // // Synopsis: Unpacks an error message from a context request // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbReceiveErrorMessage( IN PBYTE ErrorMessage, IN ULONG ErrorMessageSize, IN PKERB_CONTEXT Context, OUT PKERB_ERROR * DecodedErrorMessage, OUT PKERB_ERROR_METHOD_DATA * ErrorData ) { NTSTATUS Status = STATUS_SUCCESS; KERBERR KerbErr; PBYTE MessageStart = NULL; ULONG MessageSize = 0; BOOLEAN VerifiedHeader = FALSE; gss_OID MechId = NULL; KerbReadLockContexts(); // // For DCE style we don't use an OID // if ((Context->ContextAttributes & KERB_CONTEXT_USER_TO_USER) != 0) { MechId = gss_mech_krb5_u2u; } else { MechId = gss_mech_krb5_new; } KerbUnlockContexts(); // // First try pull off the header // MessageSize = ErrorMessageSize; MessageStart = ErrorMessage; if (!g_verify_token_header( MechId, (INT *) &MessageSize, &MessageStart, KG_TOK_CTX_ERROR, ErrorMessageSize )) { // // If we couldn't find the header, try it without // a header // MessageSize = ErrorMessageSize; MessageStart = ErrorMessage; } else { VerifiedHeader = TRUE; } KerbErr = KerbUnpackKerbError( MessageStart, MessageSize, DecodedErrorMessage ); if (!KERB_SUCCESS(KerbErr)) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; } if (((*DecodedErrorMessage)->bit_mask & error_data_present) != 0) { KerbUnpackErrorMethodData( *DecodedErrorMessage, ErrorData ); } else { Status = KerbMapKerbError(KerbErr); } Cleanup: return(Status); } #ifndef WIN32_CHICAGO //+------------------------------------------------------------------------- // // Function: KerbUnpackErrorMethodData // // Synopsis: This routine unpacks extended error information from // a KERB_ERROR message // // Effects: // // Arguments: Unpacked error message. Returns extended error to // be freed using KerbFree // // Requires: // // Returns: NTSTATUS // // Notes: // // //-------------------------------------------------------------------------- KERBERR KerbUnpackErrorMethodData( IN PKERB_ERROR ErrorMessage, IN OUT OPTIONAL PKERB_ERROR_METHOD_DATA * ppErrorData ) { PKERB_ERROR_METHOD_DATA pErrorData = NULL; KERBERR KerbErr = KDC_ERR_NONE; if (ARGUMENT_PRESENT(ppErrorData)) { *ppErrorData = NULL; } if ((ErrorMessage->bit_mask & error_data_present) == 0) { return (KRB_ERR_GENERIC); } KerbErr = KerbUnpackData( ErrorMessage->error_data.value, ErrorMessage->error_data.length, KERB_ERROR_METHOD_DATA_PDU, (void**) &pErrorData ); if (KERB_SUCCESS(KerbErr) && ARGUMENT_PRESENT(ppErrorData) && (NULL != pErrorData)) { *ppErrorData = pErrorData; pErrorData = NULL; } if (pErrorData) { KerbFreeData(KERB_ERROR_METHOD_DATA_PDU, pErrorData); } return (KerbErr); } //+------------------------------------------------------------------------- // // Function: KerbGetDnsHostName // // Synopsis: This routine gets DnsHostName of this machine. // // Effects: // // Arguments: DnsHostName - Returns the DNS Host Name of the machine. // Will return a NULL string if this machine has no DNS host name. // Free this buffer using KerbFreeString. // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbGetDnsHostName( OUT PUNICODE_STRING DnsHostName ) { NTSTATUS Status = STATUS_SUCCESS; WCHAR LocalDnsUnicodeHostName[DNS_MAX_NAME_BUFFER_LENGTH+1]; ULONG LocalDnsUnicodeHostNameLen = DNS_MAX_NAME_BUFFER_LENGTH+1; LPWSTR ConfiguredDnsName = LocalDnsUnicodeHostName; UNICODE_STRING HostName; RtlInitUnicodeString( DnsHostName, NULL ); // // Get the DNS host name. // if (!GetComputerNameEx( ComputerNameDnsHostname, ConfiguredDnsName, &LocalDnsUnicodeHostNameLen)) { goto Cleanup; } ConfiguredDnsName = &LocalDnsUnicodeHostName[LocalDnsUnicodeHostNameLen]; *ConfiguredDnsName = L'.'; ConfiguredDnsName++; // // Now get the DNS domain name // LocalDnsUnicodeHostNameLen = DNS_MAX_NAME_BUFFER_LENGTH - LocalDnsUnicodeHostNameLen; if (!GetComputerNameEx( ComputerNameDnsDomain, ConfiguredDnsName, &LocalDnsUnicodeHostNameLen )) { goto Cleanup; } RtlInitUnicodeString( &HostName, LocalDnsUnicodeHostName ); Status = RtlDowncaseUnicodeString( &HostName, &HostName, FALSE // don't allocate destination ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = KerbDuplicateString( DnsHostName, &HostName ); Cleanup: return Status; } #endif // WIN32_CHICAGO //+------------------------------------------------------------------------- // // Function: KerbIsThisOurDomain // // Synopsis: Compares a domain name to the local domain anme // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- BOOLEAN KerbIsThisOurDomain( IN PUNICODE_STRING DomainName ) { BOOLEAN Result; KerbGlobalReadLock(); Result = KerbCompareUnicodeRealmNames( DomainName, &KerbGlobalDnsDomainName ) || RtlEqualUnicodeString( DomainName, &KerbGlobalDomainName, TRUE ); KerbGlobalReleaseLock(); return(Result); } //+------------------------------------------------------------------------- // // Function: KerbGetOurDomainName // // Synopsis: Copies the machines dns domain name, if available, // netbios otherwise. // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbGetOurDomainName( OUT PUNICODE_STRING DomainName ) { NTSTATUS Status; KerbGlobalReadLock(); if (KerbGlobalDnsDomainName.Length != 0) { Status = KerbDuplicateString( DomainName, &KerbGlobalDnsDomainName ); } else { Status = KerbDuplicateString( DomainName, &KerbGlobalDomainName ); } KerbGlobalReleaseLock(); return(Status); } //+------------------------------------------------------------------------- // // Function: KerbGetGlobalRole // // Synopsis: Returns the current role of the machine // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- KERBEROS_MACHINE_ROLE KerbGetGlobalRole( VOID ) { KERBEROS_MACHINE_ROLE Role; KerbGlobalReadLock(); Role = KerbGlobalRole; KerbGlobalReleaseLock(); return(Role); } //+------------------------------------------------------------------------- // // Function: KerbSetComputerName // // Synopsis: Sets all computer-name related global variables // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbSetComputerName( VOID ) { UNICODE_STRING LocalMachineName; STRING LocalKerbMachineName; UNICODE_STRING OldMachineName; STRING OldKerbMachineName; ULONG ComputerNameLength; NTSTATUS Status; BOOLEAN LockHeld = FALSE; #ifdef WIN32_CHICAGO CHAR TempAnsiBuffer[MAX_COMPUTERNAME_LENGTH + 1]; ComputerNameLength = sizeof(TempAnsiBuffer); #endif LocalMachineName.Buffer = NULL; LocalKerbMachineName.Buffer = NULL; #ifndef WIN32_CHICAGO ComputerNameLength = 0; if (GetComputerNameW( NULL, &ComputerNameLength )) { D_DebugLog((DEB_ERROR,"Succeeded to get computer name when failure expected! %ws, line %d\n", THIS_FILE, __LINE__)); Status = STATUS_UNSUCCESSFUL; goto Cleanup; } LocalMachineName.Buffer = (LPWSTR) KerbAllocate( ((ComputerNameLength + 1) * sizeof(WCHAR)) ); if (LocalMachineName.Buffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } #endif // WIN32_CHICAGO #ifndef WIN32_CHICAGO if (!GetComputerNameW( LocalMachineName.Buffer, &ComputerNameLength )) #else // WIN32_CHICAGO if (!GetComputerName( TempAnsiBuffer, &ComputerNameLength )) #endif // WIN32_CHICAGO { D_DebugLog((DEB_ERROR,"Failed to get computer name: %d. %ws, line %d\n",GetLastError(), THIS_FILE, __LINE__)); Status = STATUS_UNSUCCESSFUL; goto Cleanup; } #ifndef WIN32_CHICAGO LocalMachineName.Length = (USHORT)(ComputerNameLength * sizeof(WCHAR)); LocalMachineName.MaximumLength = LocalMachineName.Length + sizeof(WCHAR); #else RtlCreateUnicodeStringFromAsciiz (&LocalMachineName, TempAnsiBuffer); // KerbFree (TempAnsiBuffer); #endif // WIN32_CHICAGO // // Build the ansi format // if (!KERB_SUCCESS(KerbUnicodeStringToKerbString( &LocalKerbMachineName, &LocalMachineName ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } // // free the current globals, and update to point at new values. // KerbGlobalWriteLock(); LockHeld = TRUE; OldMachineName = KerbGlobalMachineName; OldKerbMachineName = KerbGlobalKerbMachineName; KerbGlobalMachineName = LocalMachineName; KerbGlobalKerbMachineName = LocalKerbMachineName; // // now, see if the netbios machine name changed versus the prior // value. // if( OldMachineName.Buffer != NULL ) { if(!RtlEqualUnicodeString( &OldMachineName, &LocalMachineName, FALSE )) { D_DebugLog((DEB_WARN,"Netbios computer name change detected.\n")); KerbGlobalMachineNameChanged = TRUE; } } KerbGlobalReleaseLock(); LockHeld = FALSE; LocalMachineName.Buffer = NULL; LocalKerbMachineName.Buffer = NULL; KerbFreeString( &OldMachineName ); KerbFreeString( (PUNICODE_STRING)&OldKerbMachineName ); Status = STATUS_SUCCESS; Cleanup: if( LockHeld ) { KerbGlobalReleaseLock(); } KerbFreeString( &LocalMachineName ); KerbFreeString( (PUNICODE_STRING)&LocalKerbMachineName ); return Status; } // // // Routine Description: // // This function checks the system to see if // we are running on the personal version of // the operating system. // // The personal version is denoted by the product // id equal to WINNT, which is really workstation, // and the product suite containing the personal // suite string. // BOOLEAN KerbRunningPersonal( VOID ) { OSVERSIONINFOEXW OsVer = {0}; ULONGLONG ConditionMask = 0; OsVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); OsVer.wSuiteMask = VER_SUITE_PERSONAL; OsVer.wProductType = VER_NT_WORKSTATION; VER_SET_CONDITION( ConditionMask, VER_PRODUCT_TYPE, VER_EQUAL ); VER_SET_CONDITION( ConditionMask, VER_SUITENAME, VER_AND ); return RtlVerifyVersionInfo( &OsVer, VER_PRODUCT_TYPE | VER_SUITENAME, ConditionMask) == STATUS_SUCCESS; } BOOLEAN KerbRunningServer( VOID ) { OSVERSIONINFOEXW OsVer = {0}; ULONGLONG ConditionMask = 0; OsVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); OsVer.wProductType = VER_NT_DOMAIN_CONTROLLER; VER_SET_CONDITION( ConditionMask, VER_PRODUCT_TYPE, VER_GREATER_EQUAL ); return RtlVerifyVersionInfo( &OsVer, VER_PRODUCT_TYPE , ConditionMask) == STATUS_SUCCESS; } //+------------------------------------------------------------------------- // // Function: KerbSetDomainName // // Synopsis: Sets all domain-name related global variables // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbSetDomainName( IN PUNICODE_STRING DomainName, IN PUNICODE_STRING DnsDomainName, IN PSID DomainSid, IN GUID DomainGuid ) { NTSTATUS Status = STATUS_SUCCESS; BOOLEAN AcquiredLock = FALSE; UNICODE_STRING TempDomainName = {0}; UNICODE_STRING TempDnsDomainName = {0}; UNICODE_STRING TempMachineServiceName = {0}; PKERB_INTERNAL_NAME TempMitMachineServiceName = NULL; PSID TempDomainSid = NULL; UNICODE_STRING TempString; WCHAR MachineAccountName[CNLEN + 2]; // for null and '$' UNICODE_STRING DnsString = {0}; #ifndef WIN32_CHICAGO LUID SystemLogonId = SYSTEM_LUID; LUID NetworkServiceLogonId = NETWORKSERVICE_LUID; PKERB_LOGON_SESSION SystemLogonSession = NULL; PKERB_LOGON_SESSION NetworkServiceLogonSession = NULL; #endif static TimeStamp KerbSetDomainNameTime = {0}; static ULONG NumOfUpdatedLuids = 0; NtQuerySystemTime(&KerbSetDomainNameTime); // // Copy the domain name / sid // Status = KerbDuplicateString( &TempDomainName, DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = KerbDuplicateString( &TempDnsDomainName, DnsDomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // If we are in an NT domain, uppercase the dns domain name // #ifndef WIN32_CHICAGO if (DomainSid != NULL) #endif { Status = RtlUpcaseUnicodeString( &TempDnsDomainName, &TempDnsDomainName, FALSE // don't allocate ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } #ifndef WIN32_CHICAGO if (DomainSid != NULL) { Status = KerbDuplicateSid( &TempDomainSid, DomainSid ); if (!NT_SUCCESS(Status)) { goto Cleanup; } } #endif // // Create the new machine names // KerbGlobalReadLock(); ASSERT( (KerbGlobalMachineName.Length <= (CNLEN * sizeof(WCHAR)) ) ); RtlCopyMemory( MachineAccountName, KerbGlobalMachineName.Buffer, KerbGlobalMachineName.Length ); MachineAccountName[KerbGlobalMachineName.Length/sizeof(WCHAR)] = SSI_ACCOUNT_NAME_POSTFIX_CHAR; MachineAccountName[1 + KerbGlobalMachineName.Length/sizeof(WCHAR)] = L'\0'; KerbGlobalReleaseLock(); RtlInitUnicodeString( &TempString, MachineAccountName ); Status = KerbDuplicateString( &TempMachineServiceName, &TempString ); if (!NT_SUCCESS(Status)) { goto Cleanup; } #ifndef WIN32_CHICAGO // // Now build the MIT version of our machine service name // Status = KerbGetDnsHostName( &DnsString ); if (!NT_SUCCESS(Status)) { goto Cleanup; } RtlInitUnicodeString( &TempString, KERB_HOST_STRING ); if (!KERB_SUCCESS(KerbBuildFullServiceKdcName( &DnsString, &TempString, KRB_NT_SRV_HST, &TempMitMachineServiceName ))) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } // // Acquire the global lock so we can update the data // if (!KerbGlobalWriteLock()) { D_DebugLog((DEB_ERROR, "Failed to acquire global resource. Not changing domain. %ws, line %d\n", THIS_FILE, __LINE__)); goto Cleanup; } AcquiredLock = TRUE; #endif // WIN32_CHICAGO // // Copy all the data to the global structures // // If we're NT4, we don't have a dns domain name // If we're joined to an MIT domain, we don't have a domain GUID and we // have a dns domain name if ((DomainGuid == GUID_NULL) && (TempDnsDomainName.Length == 0)) { KerbGlobalDomainIsPreNT5 = TRUE; } else { KerbGlobalDomainIsPreNT5 = FALSE; } KerbFreeString(&KerbGlobalDomainName); KerbGlobalDomainName = TempDomainName; TempDomainName.Buffer = NULL; KerbFreeString(&KerbGlobalDnsDomainName); KerbGlobalDnsDomainName = TempDnsDomainName; TempDnsDomainName.Buffer = NULL; KerbFreeString(&KerbGlobalMachineServiceName); KerbGlobalMachineServiceName = TempMachineServiceName; TempMachineServiceName.Buffer = NULL; #ifndef WIN32_CHICAGO KerbFreeKdcName(&KerbGlobalMitMachineServiceName); KerbGlobalMitMachineServiceName = TempMitMachineServiceName; TempMitMachineServiceName = NULL; if (KerbGlobalDomainSid != NULL) { KerbFree(KerbGlobalDomainSid); } KerbGlobalDomainSid = TempDomainSid; TempDomainSid = NULL; // // Update the role on non DCs. The role of a DC never changes. // Demotion requires a reboot so that the domain controller role // will not change. // if (KerbGlobalRole != KerbRoleDomainController) { if (KerbRunningPersonal()) { KerbGlobalRole = KerbRoleRealmlessWksta; } else if (DomainSid == NULL ) { // No machine account, nor associate w/ MIT realm if (DnsDomainName->Length == 0 ) { KerbGlobalRole = KerbRoleRealmlessWksta; } // Member of MIT realm, but not a domain else { KerbGlobalRole = KerbRoleStandalone; } } else { KerbGlobalRole = KerbRoleWorkstation; } } KerbGlobalReleaseLock(); AcquiredLock = FALSE; // // Update the system/networkservice logon session, if there is one // SystemLogonSession = KerbReferenceLogonSession( &SystemLogonId, FALSE // don't unlink ); if (SystemLogonSession) { KerbWriteLockLogonSessions(SystemLogonSession); D_DebugLog((DEB_TRACE_CRED, "KerbSetDomainName change localsystem domain name from %wZ to %wZ\n", &SystemLogonSession->PrimaryCredentials.DomainName, DnsDomainName)); // // detect fake updates // if (!RtlEqualUnicodeString( &SystemLogonSession->PrimaryCredentials.DomainName, DnsDomainName, TRUE )) { KerbFreeString(&SystemLogonSession->PrimaryCredentials.DomainName); Status = KerbDuplicateString( &SystemLogonSession->PrimaryCredentials.DomainName , DnsDomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } KerbPurgeTicketCache(&SystemLogonSession->PrimaryCredentials.ServerTicketCache); KerbPurgeTicketCache(&SystemLogonSession->PrimaryCredentials.AuthenticationTicketCache); KerbPurgeTicketCache(&SystemLogonSession->PrimaryCredentials.S4UTicketCache); SystemLogonSession->LogonSessionFlags |= KERB_LOGON_DEFERRED; NumOfUpdatedLuids++; } KerbUnlockLogonSessions(SystemLogonSession); } NetworkServiceLogonSession = KerbReferenceLogonSession( &NetworkServiceLogonId, FALSE // don't unlink ); if (NetworkServiceLogonSession != NULL) { KerbWriteLockLogonSessions(NetworkServiceLogonSession); D_DebugLog((DEB_TRACE_CRED, "KerbSetDomainName change networkservice domain name from %wZ to %wZ\n", &NetworkServiceLogonSession->PrimaryCredentials.DomainName, DnsDomainName)); // // detect fake updates // if (!RtlEqualUnicodeString( &NetworkServiceLogonSession->PrimaryCredentials.DomainName, DnsDomainName, TRUE )) { KerbFreeString(&NetworkServiceLogonSession->PrimaryCredentials.DomainName); Status = KerbDuplicateString( &NetworkServiceLogonSession->PrimaryCredentials.DomainName , DnsDomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } KerbPurgeTicketCache(&NetworkServiceLogonSession->PrimaryCredentials.ServerTicketCache); KerbPurgeTicketCache(&NetworkServiceLogonSession->PrimaryCredentials.AuthenticationTicketCache); KerbPurgeTicketCache(&NetworkServiceLogonSession->PrimaryCredentials.S4UTicketCache); NetworkServiceLogonSession->LogonSessionFlags |= KERB_LOGON_DEFERRED; NumOfUpdatedLuids++; } KerbUnlockLogonSessions(NetworkServiceLogonSession); } #endif Cleanup: #ifndef WIN32_CHICAGO if (AcquiredLock) { KerbGlobalReleaseLock(); } #endif // WIN32_CHICAGO KerbFreeString( &DnsString ); KerbFreeString( &TempDomainName ); KerbFreeString( &TempDnsDomainName ); KerbFreeString( &TempMachineServiceName ); #ifndef WIN32_CHICAGO KerbFreeKdcName( &TempMitMachineServiceName ); if (TempDomainSid != NULL) { KerbFree(TempDomainSid); } if (SystemLogonSession != NULL) { KerbDereferenceLogonSession(SystemLogonSession); } if (NetworkServiceLogonSession != NULL) { KerbDereferenceLogonSession(NetworkServiceLogonSession); } #endif return(Status); } #ifndef WIN32_CHICAGO //+------------------------------------------------------------------------- // // Function: KerbLoadKdc // // Synopsis: Loads kdcsvc.dll and gets the address of important functions // // Effects: // // Arguments: // // Requires: // // Returns: STATUS_SUCCESS if kdcsvc.dll could be loaded and the // necessary entrypoints found. // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbLoadKdc( VOID ) { NTSTATUS Status = STATUS_SUCCESS; KerbKdcHandle = LoadLibraryA("kdcsvc.dll"); if (KerbKdcHandle == NULL) { D_DebugLog((DEB_WARN,"Failed to load kdcsvc.dll: %d\n",GetLastError())); return(STATUS_DLL_NOT_FOUND); } KerbKdcVerifyPac = (PKDC_VERIFY_PAC_ROUTINE) GetProcAddress( KerbKdcHandle, KDC_VERIFY_PAC_NAME ); if (KerbKdcVerifyPac == NULL) { D_DebugLog((DEB_WARN, "Failed to get proc address for KdcVerifyPac: %d\n", GetLastError())); Status = STATUS_PROCEDURE_NOT_FOUND; goto Cleanup; } KerbKdcGetTicket = (PKDC_GET_TICKET_ROUTINE) GetProcAddress( KerbKdcHandle, KDC_GET_TICKET_NAME ); if (KerbKdcGetTicket == NULL) { D_DebugLog((DEB_WARN,"Failed to get proc address for KdcGetTicket: %d\n", GetLastError())); Status = STATUS_PROCEDURE_NOT_FOUND; goto Cleanup; } KerbKdcChangePassword = (PKDC_GET_TICKET_ROUTINE) GetProcAddress( KerbKdcHandle, KDC_CHANGE_PASSWORD_NAME ); if (KerbKdcChangePassword == NULL) { D_DebugLog((DEB_WARN,"Failed to get proc address for KdcChangePassword: %d\n", GetLastError())); Status = STATUS_PROCEDURE_NOT_FOUND; goto Cleanup; } KerbKdcFreeMemory = (PKDC_FREE_MEMORY_ROUTINE) GetProcAddress( KerbKdcHandle, KDC_FREE_MEMORY_NAME ); if (KerbKdcFreeMemory == NULL) { D_DebugLog((DEB_WARN,"Failed to get proc address for KdcFreeMemory: %d\n", GetLastError())); Status = STATUS_PROCEDURE_NOT_FOUND; goto Cleanup; } Cleanup: if (!NT_SUCCESS(Status)) { if (KerbKdcHandle != NULL) { FreeLibrary(KerbKdcHandle); KerbKdcHandle = NULL; } } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbDomainChangeCallback // // Synopsis: Function to be called when domain changes // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- VOID NTAPI KerbDomainChangeCallback( IN POLICY_NOTIFICATION_INFORMATION_CLASS ChangedInfoClass ) { NTSTATUS Status = STATUS_SUCCESS; PLSAPR_POLICY_INFORMATION Policy = NULL; // // We only care about domain dns information // if (ChangedInfoClass != PolicyNotifyDnsDomainInformation) { return; } // // Get the new domain information // Status = I_LsaIQueryInformationPolicyTrusted( PolicyDnsDomainInformation, &Policy ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to query domain dns information %x - not updating. %ws, line %d\n", Status, THIS_FILE, __LINE__)); goto Cleanup; } // // update computer name info. // Status = KerbSetComputerName(); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to set computer name: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; } Status = KerbSetDomainName( (PUNICODE_STRING) &Policy->PolicyDnsDomainInfo.Name, (PUNICODE_STRING) &Policy->PolicyDnsDomainInfo.DnsDomainName, (PSID) Policy->PolicyDnsDomainInfo.Sid, (GUID) Policy->PolicyDnsDomainInfo.DomainGuid ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to set domain name: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); goto Cleanup; } // // Domain Name has changed. So, the cache in the registry will // have changed too. And we haven't rebooted yet. // KerbSetKdcData(TRUE, FALSE); Cleanup: if (Policy != NULL) { I_LsaIFree_LSAPR_POLICY_INFORMATION( PolicyDnsDomainInformation, Policy ); } return; } //+------------------------------------------------------------------------- // // Function: KerbRegisterForDomainChange // // Synopsis: Register with the LSA to be notified of domain changes // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbRegisterForDomainChange( VOID ) { NTSTATUS Status = STATUS_SUCCESS; Status = I_LsaIRegisterPolicyChangeNotificationCallback( KerbDomainChangeCallback, PolicyNotifyDnsDomainInformation ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to register for domain change notification: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__)); } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbUnregisterForDomainChange // // Synopsis: Unregister for domain change notification // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- VOID KerbUnregisterForDomainChange( VOID ) { (VOID) I_LsaIUnregisterPolicyChangeNotificationCallback( KerbDomainChangeCallback, PolicyNotifyDnsDomainInformation ); } //+------------------------------------------------------------------------- // // Function: KerbUpdateGlobalAddresses // // Synopsis: Updates the global array of addresses with information from // netlogon. // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: Ideally, we should also have called WSAProviderConfigChange to be // notified of when tcp is added/removed. But, we can take advantage // of the fact that netlogon is registered for changes in ipaddress // so, even though, change of ipaddress & xports notifications are // async, we will get to know of it, so, it suffices to rely on // netlogon rather than register for a notification change. // // //-------------------------------------------------------------------------- NTSTATUS KerbUpdateGlobalAddresses( IN PSOCKET_ADDRESS NewAddresses, IN ULONG NewAddressCount ) { PSOCKADDR_IN GlobalIpAddresses = NULL; ULONG Index; ULONG AddressCount = 0; WSAPROTOCOL_INFO *lpProtocolBuf = NULL; DWORD dwBufLen = 0; INT protocols[2]; int nRet = 0; BOOLEAN NoTcpInstalled = FALSE; GlobalIpAddresses = (PSOCKADDR_IN) KerbAllocate(sizeof(SOCKADDR_IN) * NewAddressCount); if (GlobalIpAddresses == NULL) { return(STATUS_INSUFFICIENT_RESOURCES); } for (Index = 0; Index < NewAddressCount ; Index++ ) { if ((NewAddresses[Index].iSockaddrLength == sizeof(SOCKADDR)) && (NewAddresses[Index].lpSockaddr->sa_family == AF_INET)) { RtlCopyMemory( &GlobalIpAddresses[AddressCount++], NewAddresses[Index].lpSockaddr, sizeof(SOCKADDR_IN) ); } else { D_DebugLog((DEB_ERROR,"Netlogon handed us a address of length or type %d, %d. %ws, line %d\n", NewAddresses[Index].iSockaddrLength, NewAddresses[Index].lpSockaddr->sa_family, THIS_FILE, __LINE__ )); } } // // winsock is already initialized by now, or we would not have // gotten so far. Check if TCP s an available xport // protocols[0] = IPPROTO_TCP; protocols[1] = NULL; nRet = WSAEnumProtocols(protocols, lpProtocolBuf, &dwBufLen); if (nRet == 0) { // // Tcp is not installed as a xport. // D_DebugLog((DEB_TRACE_SOCK,"WSAEnumProtocols returned 0x%x. %ws, line %d\n", nRet, THIS_FILE, __LINE__ )); NoTcpInstalled = TRUE; } // // Copy them into the global for others to use // KerbGlobalWriteLock(); if (KerbGlobalIpAddresses != NULL) { KerbFree(KerbGlobalIpAddresses); } KerbGlobalIpAddresses = GlobalIpAddresses; KerbGlobalIpAddressCount = AddressCount; KerbGlobalIpAddressesInitialized = TRUE; KerbGlobalNoTcpUdp = NoTcpInstalled; KerbGlobalReleaseLock(); return(STATUS_SUCCESS); } #ifdef RESTRICTED_TOKEN //+------------------------------------------------------------------------- // // Function: KerbGetTokenInformation // // Synopsis: Allocates and returns token information // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbGetTokenInformation( IN HANDLE TokenHandle, IN TOKEN_INFORMATION_CLASS InformationClass, IN OUT PVOID * Buffer ) { NTSTATUS Status = STATUS_SUCCESS; ULONG BufferSize = 0; // // First retrieve the restricted sids // Status = NtQueryInformationToken( TokenHandle, InformationClass, NULL, 0, &BufferSize ); if (Status != STATUS_BUFFER_TOO_SMALL) { goto Cleanup; } *Buffer = KerbAllocate(BufferSize); if (*Buffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } Status = NtQueryInformationToken( TokenHandle, InformationClass, *Buffer, BufferSize, &BufferSize ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Cleanup: return(Status); } //+------------------------------------------------------------------------- // // Function: KerbCaptureTokenRestrictions // // Synopsis: Captures the restrictions of a restricted token // // Effects: // // Arguments: TokenHandle - token handle open for TOKEN_QUERY access // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbCaptureTokenRestrictions( IN HANDLE TokenHandle, OUT PKERB_AUTHORIZATION_DATA Restrictions ) { PTOKEN_GROUPS Groups = NULL; PTOKEN_GROUPS RestrictedSids = NULL; PTOKEN_PRIVILEGES Privileges = NULL; PTOKEN_PRIVILEGES DeletePrivileges = NULL; NTSTATUS Status = STATUS_SUCCESS; ULONG BufferSize = 0; ULONG Index,Index2,LastIndex; KERB_TOKEN_RESTRICTIONS TokenRestrictions = {0}; Status = KerbGetTokenInformation( TokenHandle, TokenRestrictedSids, (PVOID *) &RestrictedSids ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = KerbGetTokenInformation( TokenHandle, TokenGroups, (PVOID *) &Groups ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = KerbGetTokenInformation( TokenHandle, TokenPrivileges, (PVOID *) &Privileges ); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Now build the list of just the restricted privileges & groups // // // First, find what groups are restricted. // LastIndex = 0; for (Index = 0; Index < Groups->GroupCount ; Index++ ) { if ((Groups->Groups[Index].Attributes & SE_GROUP_USE_FOR_DENY_ONLY) != 0) { if (LastIndex != Index) { Groups->Groups[LastIndex].Sid = Groups->Groups[Index].Sid; Groups->Groups[LastIndex].Attributes = 0; } LastIndex++; } } Groups->GroupCount = LastIndex; if (LastIndex != 0) { TokenRestrictions.GroupsToDisable = (PPAC_TOKEN_GROUPS) Groups; TokenRestrictions.Flags |= KERB_TOKEN_RESTRICTION_DISABLE_GROUPS; } // // Add the restricted sids // if (RestrictedSids->GroupCount != 0) { for (Index = 0; Index < RestrictedSids->GroupCount ; Index++ ) { RestrictedSids->Groups[Index].Attributes = 0; } TokenRestrictions.RestrictedSids = (PPAC_TOKEN_GROUPS) RestrictedSids; TokenRestrictions.Flags |= KERB_TOKEN_RESTRICTION_RESTRICT_SIDS; } // // Now make a list of all the privileges that _aren't_ enabled // SafeAllocaAllocate(DeletePrivileges, sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES) * (1 + SE_MAX_WELL_KNOWN_PRIVILEGE - SE_MIN_WELL_KNOWN_PRIVILEGE)); if (DeletePrivileges == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } DeletePrivileges->PrivilegeCount = 0; // // Find out what privileges haven't been enabled // LastIndex = 0; for (Index = SE_MIN_WELL_KNOWN_PRIVILEGE; Index <= SE_MAX_WELL_KNOWN_PRIVILEGE ; Index++ ) { LUID TempLuid; BOOLEAN Found = FALSE; TempLuid = RtlConvertUlongToLuid(Index); for (Index2 = 0; Index2 < Privileges->PrivilegeCount ; Index2++ ) { if (RtlEqualLuid(&Privileges->Privileges[Index2].Luid,&TempLuid) && ((Privileges->Privileges[Index2].Attributes & SE_PRIVILEGE_ENABLED) != 0)) { Found = TRUE; break; } } if (!Found) { DeletePrivileges->Privileges[LastIndex].Luid = TempLuid; DeletePrivileges->Privileges[LastIndex].Attributes = 0; LastIndex++; } } DeletePrivileges->PrivilegeCount = LastIndex; if (LastIndex != 0) { TokenRestrictions.PrivilegesToDelete = (PPAC_TOKEN_PRIVILEGES) DeletePrivileges; TokenRestrictions.Flags |= KERB_TOKEN_RESTRICTION_DELETE_PRIVS; } Restrictions->value.auth_data_type = KERB_AUTH_DATA_TOKEN_RESTRICTIONS; Status = PAC_EncodeTokenRestrictions( &TokenRestrictions, &Restrictions->value.auth_data.value, &Restrictions->value.auth_data.length ); Cleanup: if (Groups != NULL) { KerbFree(Groups); } if (RestrictedSids != NULL) { KerbFree(RestrictedSids); } if (Privileges != NULL) { KerbFree(Privileges); } SafeAllocaFree(DeletePrivileges); return(Status); } //+------------------------------------------------------------------------- // // Function: KerbAddRestrictionsToCredential // // Synopsis: Captures client'st token restrictions and sticks them in // the credential // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbAddRestrictionsToCredential( IN PKERB_LOGON_SESSION LogonSession, IN PKERB_CREDENTIAL Credential ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_AUTHORIZATION_DATA AuthData = NULL; BOOLEAN CrossRealm; PKERB_TICKET_CACHE_ENTRY ExistingTgt = NULL; HANDLE ClientToken = NULL; PKERB_INTERNAL_NAME ServiceName = NULL; UNICODE_STRING ServiceRealm = NULL_UNICODE_STRING; PKERB_KDC_REPLY KdcReply = NULL; PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody = NULL; BOOLEAN TicketCacheLocked = FALSE; PKERB_TICKET_CACHE_ENTRY NewTicket = NULL; ULONG CacheFlags = 0; BOOLEAN UseSuppliedCreds = FALSE; // // Capture the existing TGT // Status = LsaFunctions->ImpersonateClient(); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = NtOpenThreadToken( NtCurrentThread(), TOKEN_QUERY, TRUE, // open as self &ClientToken ); if (!NT_SUCCESS(Status)) { goto Cleanup; } RevertToSelf(); // // Capture the restrictions for this token // AuthData = (PKERB_AUTHORIZATION_DATA) MIDL_user_allocate(sizeof(KERB_AUTHORIZATION_DATA)); if (AuthData == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } Status = KerbCaptureTokenRestrictions( ClientToken, AuthData ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to capture token restrictions: 0x%x\n",Status)); goto Cleanup; } KerbWriteLockLogonSessions(LogonSession); Credential->AuthData = AuthData; // // Turn off tgt avail to force us to get a new tgt // Credential->CredentialFlags &= ~KERB_CRED_TGT_AVAIL; AuthData = NULL; KerbUnlockLogonSessions(LogonSession); Cleanup: if (AuthData != NULL) { if (AuthData->value.auth_data.value != NULL) { MIDL_user_free(AuthData->value.auth_data.value); } MIDL_user_free(AuthData); } if (ClientToken != NULL) { NtClose(ClientToken); } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbBuildTokenRestrictionAuthData // // Synopsis: // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: Not called yet - used for restricted tickets // // //-------------------------------------------------------------------------- NTSTATUS KerbBuildEncryptedAuthData( OUT PKERB_ENCRYPTED_DATA EncryptedAuthData, IN PKERB_TICKET_CACHE_ENTRY Ticket, IN PKERB_AUTHORIZATION_DATA PlainAuthData ) { KERBERR KerbErr; NTSTATUS Status = STATUS_SUCCESS; KERB_MESSAGE_BUFFER PackedAuthData = {0}; KerbErr = KerbPackData( &PlainAuthData, PKERB_AUTHORIZATION_DATA_LIST_PDU, &PackedAuthData.BufferSize, &PackedAuthData.Buffer ); if (!KERB_SUCCESS(KerbErr)) { Status = KerbMapKerbError(KerbErr); goto Cleanup; } KerbReadLockTicketCache(); KerbErr = KerbAllocateEncryptionBufferWrapper( Ticket->SessionKey.keytype, PackedAuthData.BufferSize, &EncryptedAuthData->cipher_text.length, &EncryptedAuthData->cipher_text.value ); if (KERB_SUCCESS(KerbErr)) { KerbErr = KerbEncryptDataEx( EncryptedAuthData, PackedAuthData.BufferSize, PackedAuthData.Buffer, KERB_NO_KEY_VERSION, KERB_TGS_REQ_SESSKEY_SALT, // was KERB_NON_KERB_SALT need to check for KERB_TGS_REQ_SUBKEY_SALT also &Ticket->SessionKey ); } KerbUnlockTicketCache(); if (!KERB_SUCCESS(KerbErr)) { Status = KerbMapKerbError(KerbErr); goto Cleanup; } Cleanup: if (PackedAuthData.Buffer != NULL) { MIDL_user_free(PackedAuthData.Buffer); } return(Status); } //+------------------------------------------------------------------------- // // Function: KerbGetRestrictedTgtForCredential // // Synopsis: // // Effects: // // Arguments: // // Requires: // // Returns: // // Notes: // // //-------------------------------------------------------------------------- NTSTATUS KerbGetRestrictedTgtForCredential( IN PKERB_LOGON_SESSION LogonSession, IN PKERB_CREDENTIAL Credential ) { NTSTATUS Status = STATUS_SUCCESS; PKERB_AUTHORIZATION_DATA AuthData = NULL; BOOLEAN CrossRealm; PKERB_TICKET_CACHE_ENTRY ExistingTgt = NULL; PKERB_INTERNAL_NAME ServiceName = NULL; UNICODE_STRING ServiceRealm = NULL_UNICODE_STRING; PKERB_KDC_REPLY KdcReply = NULL; PKERB_ENCRYPTED_KDC_REPLY KdcReplyBody = NULL; BOOLEAN TicketCacheLocked = FALSE; PKERB_TICKET_CACHE_ENTRY NewTicket = NULL; ULONG CacheFlags = 0, RetryFlags = 0; BOOLEAN UseSuppliedCreds = FALSE; // // First get an old TGT // KerbReadLockLogonSessions(LogonSession); if (Credential->SuppliedCredentials == NULL) { ULONG Flags; // // We don't have supplied creds, but we need them, so copy // from the logon session. // Status = KerbCaptureSuppliedCreds( LogonSession, NULL, // no auth data NULL, // no principal name &Credential->SuppliedCredentials, &Flags ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to capture dummy supplied creds: 0x%x\n",Status)); KerbUnlockLogonSessions(LogonSession); goto Cleanup; } AuthData = Credential->AuthData; } else { UseSuppliedCreds = FALSE; } DsysAssert(Credential->SuppliedCredentials != NULL); Status = KerbGetTgtForService( LogonSession, (UseSuppliedCreds) ? Credential : NULL, NULL, NULL, // no SuppRealm &Credential->SuppliedCredentials->DomainName, &ExistingTgt, &CrossRealm ); KerbUnlockLogonSessions(LogonSession); if (!NT_SUCCESS(Status)) { goto Cleanup; } // // Now get a new TGT with this ticket // // // Copy the names out of the input structures so we can // unlock the structures while going over the network. // DsysAssert( !TicketCacheLocked ); KerbReadLockTicketCache(); TicketCacheLocked = TRUE; // // If the renew time is not much bigger than the end time, don't bother // renewing // Status = KerbDuplicateString( &ServiceRealm, &ExistingTgt->DomainName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } Status = KerbDuplicateKdcName( &ServiceName, ExistingTgt->ServiceName ); if (!NT_SUCCESS(Status)) { goto Cleanup; } CacheFlags = ExistingTgt->CacheFlags; DsysAssert( TicketCacheLocked ); KerbUnlockTicketCache(); TicketCacheLocked = FALSE; Status = KerbGetTgsTicket( &ServiceRealm, ExistingTgt, ServiceName, FALSE, 0, // no ticket optiosn 0, // no encryption type AuthData, // no authorization data NULL, // no tgt reply NULL, NULL, &KdcReply, &KdcReplyBody, &RetryFlags ); if (!NT_SUCCESS(Status)) { D_DebugLog((DEB_ERROR,"Failed to get restricted tgs ticket: 0x%x\n",Status)); goto Cleanup; } // // Now we want to purge the existing ticket cache and add this ticket // KerbPurgeTicketCache( &Credential->SuppliedCredentials->AuthenticationTicketCache ); KerbPurgeTicketCache( &Credential->SuppliedCredentials->ServerTicketCache ); KerbReadLockLogonSessions(LogonSession); Status = KerbCreateTicketCacheEntry( KdcReply, KdcReplyBody, ServiceName, &ServiceRealm, CacheFlags, &Credential->SuppliedCredentials->AuthenticationTicketCache, NULL, // no credential key &NewTicket ); KerbUnlockLogonSessions(LogonSession); Cleanup: if (TicketCacheLocked) { KerbUnlockTicketCache(); } if (ExistingTgt != NULL) { KerbDereferenceTicketCacheEntry(ExistingTgt); } if (NewTicket != NULL) { KerbDereferenceTicketCacheEntry(NewTicket); } KerbFreeTgsReply(KdcReply); KerbFreeKdcReplyBody(KdcReplyBody); KerbFreeKdcName(&ServiceName); KerbFreeString(&ServiceRealm); return(Status); } #endif #endif // WIN32_CHICAGO