/*++ Copyright (c) 1987-1996 Microsoft Corporation Module Name: ssiapi.c Abstract: Authentication and replication API routines (server side). Author: Cliff Van Dyke (cliffv) 28-Jun-1991 Environment: User mode only. Contains NT-specific code. Requires ANSI C extensions: slash-slash comments, long external names. Revision History: 02-Jan-1992 (madana) added support for builtin/multidomain replication. --*/ // // Common include files. // #include "logonsrv.h" // Include files common to entire service #pragma hdrstop // // Include files specific to this .c file // #include "lsarepl.h" // PackLsa .. #include #include #include #include #include #include // NetpSrv... #include // LDAP_SERVER_PERMISSIVE_MODIFY_OID_W // // Define the maximum number of deltas returned on any single call // // Theoretically, MaxNumDeltas should be some function of // PreferredMaximumLength. However, by the time you allow for // the large swing in PreferredMaximumLength allowed by the BDC replication // Governor and then not wanting this buffer to be ridiculously large // when the full 128K is asked for, we find that 1000 entries is always // a reasonable compromise. // #define MAX_DELTA_COUNT 1000 // // Maximum number of deltas that can be generated by a single change log entry. // #define MAX_DELTAS_PER_CHANGELOG 4 // // Host prefix for SPNs // #define NL_HOST_PREFIX L"HOST/" // // work record for SPN update: // typedef struct _NL_SPN_UPDATE { BOOLEAN SetSpn; BOOLEAN SetDnsHostName; BOOLEAN WriteEventLogOnFailure; LPWSTR DnsHostName; LPWSTR NetbiosComputerName; LPWSTR UncDcName; LPWSTR NetbiosDomainName; LPWSTR DnsDomainName; } NL_SPN_UPDATE, * PNL_SPN_UPDATE ; // // Challenge data struct and relevant defines // typedef struct _NL_CHALLENGE { // // Link to next challenge entry in NlGlobalChallengeList // (Serialized by NlGlobalChallengeCritSect) // LIST_ENTRY ChNext; // // Challenge sent by the client // NETLOGON_CREDENTIAL ChClientChallenge; // // Challenge returned by the server (us) // NETLOGON_CREDENTIAL ChServerChallenge; // // Time stampt when the challenge entry got created // ULONG ChSetTime; // // Name(s) of the account that the client used to // unsuccessfully authenticate on this challenge. // May be NULL. // LPWSTR ChFailedAccountName; // // The name of the client (must be the last field) // WCHAR ChClientName[ANYSIZE_ARRAY]; } NL_CHALLENGE, *PNL_CHALLENGE; // Lifetime of a challenge entry in the global list of // outstanding challenges #define NL_CHALLENGE_TIMEOUT 120000 // 2*60*1000 = 2 minutes // Maximum number of challenges we are going to keep // before we start throwing away existing ones at random #define NL_MAX_CHALLENGE_COUNT 100000 // Number of challenges at which we reschedule the scavenger // to run soon (as given by NL_LARGE_CHALLENGE_TIMEOUT). // Running the scavenger soon will prevent us from commiting // a lot of memory for an extended period for challenges coming // from a malicious client making tons of challenge requests. // #define NL_LARGE_CHALLENGE_COUNT 5000 // Timeout when the scavenger will be rescheduled to run // upon detection of current large number of outstanding // challenges (provided the scavenger isn't already sheduled // to run earlier). #define NL_LARGE_CHALLENGE_COUNT_TIMEOUT 120000 // 2*60*1000 = 2 minutes VOID NlScavengeOldChallenges( VOID ) /*++ Routine Description: This function removes all expired challenge entries from the global list of outstanding challenges. Arguments: None Return Value: None --*/ { NTSTATUS Status = STATUS_ACCESS_DENIED; PLIST_ENTRY ChallengeEntry = NULL; PNL_CHALLENGE Challenge = NULL; ULONG CurrentTime; ULONG ElapsedTime; LPWSTR MsgStrings[3]; CurrentTime = GetTickCount(); EnterCriticalSection( &NlGlobalChallengeCritSect ); ChallengeEntry = NlGlobalChallengeList.Flink; while ( ChallengeEntry != &NlGlobalChallengeList ) { Challenge = CONTAINING_RECORD( ChallengeEntry, NL_CHALLENGE, ChNext ); ChallengeEntry = ChallengeEntry->Flink; // // If time has wrapped, account for it // if ( CurrentTime >= Challenge->ChSetTime ) { ElapsedTime = CurrentTime - Challenge->ChSetTime; } else { ElapsedTime = (0xFFFFFFFF - Challenge->ChSetTime) + CurrentTime; } // // The list of challenges is sorted by the time stampt. // So if this entry is old, remove it. Otherwise, we have // removed all expired entries, so break from the loop. // if ( ElapsedTime >= NL_CHALLENGE_TIMEOUT ) { // // Write the event log stating that this client // failed to authenticate. Note that since we avoid // duplicate event logs, there will be only one // message (which is good) for multiple challenges // from the same client for the same account. // MsgStrings[0] = Challenge->ChClientName; // // If the account name is available, log it // if ( Challenge->ChFailedAccountName != NULL ) { MsgStrings[1] = Challenge->ChFailedAccountName; MsgStrings[2] = (LPWSTR) LongToPtr( Status ); NlpWriteEventlog( NELOG_NetlogonServerAuthFailed, EVENTLOG_ERROR_TYPE, (LPBYTE) & Status, sizeof(Status), MsgStrings, 3 | NETP_LAST_MESSAGE_IS_NTSTATUS ); } else { MsgStrings[1] = (LPWSTR) LongToPtr( Status ); NlpWriteEventlog( NELOG_NetlogonServerAuthFailedNoAccount, EVENTLOG_ERROR_TYPE, (LPBYTE) & Status, sizeof(Status), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NTSTATUS ); } // // Delink and free this entry // if ( Challenge->ChFailedAccountName != NULL ) { LocalFree( Challenge->ChFailedAccountName ); } RemoveEntryList(&Challenge->ChNext); LocalFree( Challenge ); NlGlobalChallengeCount --; } else { break; } } LeaveCriticalSection( &NlGlobalChallengeCritSect ); } NTSTATUS NlInsertChallenge( IN LPWSTR ClientName, IN PNETLOGON_CREDENTIAL ClientChallenge, IN PNETLOGON_CREDENTIAL ServerChallenge ) /*++ Routine Description: This function inserts a pair of client/server challenges into the global list of outstanding challenges. Arguments: ClientName -- Name of the client supplying the client challenge. ClientCredential -- 64 bit challenge supplied by the client. ServerCredential -- Server challenge response returned by us Return Value: STATUS_SUCCESS -- A new challenge entry was successfully added STATUS_NO_MEMORY -- The was not enough memory --*/ { PLIST_ENTRY ChallengeEntry = NULL; PNL_CHALLENGE NewChallenge = NULL; PNL_CHALLENGE Challenge = NULL; BOOL RescheduleScavenger = FALSE; // // Allocate a new challenge entry // NewChallenge = LocalAlloc( LMEM_ZEROINIT, sizeof(NL_CHALLENGE) + (wcslen(ClientName) + 1) * sizeof(WCHAR) ); if ( NewChallenge == NULL ) { return STATUS_NO_MEMORY; } // // Fill it in // NewChallenge->ChSetTime = GetTickCount(); RtlCopyMemory( &NewChallenge->ChClientName, ClientName, (wcslen(ClientName) + 1) * sizeof(WCHAR) ); NewChallenge->ChClientChallenge = *ClientChallenge; NewChallenge->ChServerChallenge = *ServerChallenge; EnterCriticalSection( &NlGlobalChallengeCritSect ); // // First scavenge old entries // NlScavengeOldChallenges(); // // Now determine if we have exceeded the limit // on the total number of outstanding challenges // if ( NlGlobalChallengeCount >= NL_MAX_CHALLENGE_COUNT ) { ULONG Index = 0; ULONG RemoveEntryNumber; // // Pick up an entry at random and free it // RemoveEntryNumber = rand() % NL_MAX_CHALLENGE_COUNT; // // Locate that entry in the list and remove it // for ( ChallengeEntry = NlGlobalChallengeList.Flink; ChallengeEntry != &NlGlobalChallengeList; ChallengeEntry = ChallengeEntry->Flink ) { if ( Index == RemoveEntryNumber ) { Challenge = CONTAINING_RECORD( ChallengeEntry, NL_CHALLENGE, ChNext ); NlPrint(( NL_CRITICAL, "NlInsertChallenge: Removing challenge %ld for %ws\n", Index, Challenge->ChClientName )); RemoveEntryList( &Challenge->ChNext ); LocalFree( Challenge ); NlGlobalChallengeCount --; break; } Index ++; } } // // Finally insert into the list at the tail to keep the list // sorted by the tick count. // // Note that the tick count can't be reset, so the list will // stay always sorted. The tick count can wrap, however, // but we will take care of it when we calculate the elapsed time. // InsertTailList( &NlGlobalChallengeList, &NewChallenge->ChNext ); NlGlobalChallengeCount ++; // // If we have too many challenges, // reschedule the scavenger to run soon. // This way we don't commit large amount of // memory for an extended period of time for tons // of challenges comming from a malicious caller. // if ( NlGlobalChallengeCount >= NL_LARGE_CHALLENGE_COUNT ) { RescheduleScavenger = TRUE; NlPrint(( NL_CRITICAL, "NlInsertChallenge: Too many challenges: %lu. Will start scavenger in 2 mins\n", NlGlobalChallengeCount )); } LeaveCriticalSection( &NlGlobalChallengeCritSect ); // // Reschedule the scavenger as needed // if ( RescheduleScavenger ) { LARGE_INTEGER TimeNow; DWORD Timeout = 0xFFFFFFFF; EnterCriticalSection( &NlGlobalScavengerCritSect ); NlQuerySystemTime( &TimeNow ); if ( !TimerExpired(&NlGlobalScavengerTimer, &TimeNow, &Timeout) ) { if ( Timeout > NL_LARGE_CHALLENGE_COUNT_TIMEOUT ) { NlGlobalScavengerTimer.Period -= (Timeout - NL_LARGE_CHALLENGE_COUNT_TIMEOUT); if ( !SetEvent( NlGlobalTimerEvent ) ) { NlPrint(( NL_CRITICAL, "NlInsertChallenge: SetEvent failed %ld\n", GetLastError() )); } } } LeaveCriticalSection( &NlGlobalScavengerCritSect ); } return STATUS_SUCCESS; } VOID NlRemoveChallenge( IN LPWSTR ClientName OPTIONAL, IN LPWSTR AccountName OPTIONAL, IN BOOL InterdomainTrustAccount ) /*++ Routine Description: This function removes challenge entries from the global list of outstanding challenges. Arguments: ClientName -- Name of the client whose associated challenges entries will be removed. If NULL, all entries in the list will be removed. AccountName -- Name of teh account used by the client to athenticate with this server. Used only if ClinetName is specified. InterdomainTrustAccount -- TRUE if the client used an interdomain trust account to set up a secure channel. Used only if ClientName is specified. Return Value: None --*/ { NTSTATUS Status = STATUS_ACCESS_DENIED; PLIST_ENTRY ChallengeEntry = NULL; PNL_CHALLENGE Challenge = NULL; ULONG SameAccountChallengeCount = 0; BOOLEAN LogEvent = FALSE; LPWSTR MsgStrings[3]; // // First scavenge old entries from the head of the list // Skip this step if we are removing all entries anyway // EnterCriticalSection( &NlGlobalChallengeCritSect ); if ( ClientName != NULL ) { NlScavengeOldChallenges(); } // // Next remove all entries in the list associated with client name // ChallengeEntry = NlGlobalChallengeList.Flink; while ( ChallengeEntry != &NlGlobalChallengeList ) { Challenge = CONTAINING_RECORD( ChallengeEntry, NL_CHALLENGE, ChNext ); ChallengeEntry = ChallengeEntry->Flink; // // If the client name is NULL, we are shutting down, // so just delink and free all entries // if ( ClientName == NULL ) { if ( Challenge->ChFailedAccountName != NULL ) { LocalFree( Challenge->ChFailedAccountName ); } RemoveEntryList(&Challenge->ChNext); LocalFree( Challenge ); NlGlobalChallengeCount --; // // If this entry is for the specified client, // process it // } else if ( NlNameCompare(ClientName, Challenge->ChClientName, NAMETYPE_COMPUTER) == 0 ) { MsgStrings[0] = Challenge->ChClientName; // // If this entry has an account different from // the specied one, log an event for it. // Note that since we avoid duplicate event logs, // there will be only one message (which is good) // for multiple challenges from the same client // for the same account. // if ( AccountName != NULL && Challenge->ChFailedAccountName != NULL && _wcsicmp(Challenge->ChFailedAccountName, AccountName) != 0 ) { MsgStrings[1] = Challenge->ChFailedAccountName; MsgStrings[2] = (LPWSTR) LongToPtr( Status ); NlpWriteEventlog( NELOG_NetlogonServerAuthFailed, EVENTLOG_ERROR_TYPE, (LPBYTE) & Status, sizeof(Status), MsgStrings, 3 | NETP_LAST_MESSAGE_IS_NTSTATUS ); // // Otherwise, count this entry in the number // of challenges with the specified or empty // account names (a challenge may have an empty // account name if we haven't reached it in the // authentication try loop). // } else { SameAccountChallengeCount ++; } // // Delink this entry and free it // if ( Challenge->ChFailedAccountName != NULL ) { LocalFree( Challenge->ChFailedAccountName ); } RemoveEntryList(&Challenge->ChNext); LocalFree( Challenge ); NlGlobalChallengeCount --; } } LeaveCriticalSection( &NlGlobalChallengeCritSect ); // // If there are more than a certain number of challenges // with the specified or emppty account, some other // (possibly malicious) client attempted to authenticate // using this account. Log an event for this. Don't specify // the account name as we don't know which account that // client would specify. // // For interdomain trust, the client may legitimately // try up to 3 times (passwords new, old, from PDC). // Otherwise, it gets 2 tries (new and old pwd). // if ( InterdomainTrustAccount ) { if ( SameAccountChallengeCount > 3 ) { LogEvent = TRUE; } } else { if ( SameAccountChallengeCount > 2 ) { LogEvent = TRUE; } } if ( LogEvent ) { MsgStrings[0] = ClientName; MsgStrings[1] = (LPWSTR) LongToPtr( Status ); NlpWriteEventlog( NELOG_NetlogonServerAuthFailedNoAccount, EVENTLOG_ERROR_TYPE, (LPBYTE) & Status, sizeof(Status), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NTSTATUS ); } } NTSTATUS NetrServerReqChallenge( IN LPWSTR PrimaryName OPTIONAL, IN LPWSTR ComputerName, IN PNETLOGON_CREDENTIAL ClientChallenge, OUT PNETLOGON_CREDENTIAL ServerChallenge ) /*++ Routine Description: This is the server side of I_NetServerReqChallenge. I_NetServerReqChallenge is the first of two functions used by a client Netlogon service to authenticate with another Netlogon service. (See I_NetServerAuthenticate below.) This function passes a challenge to the DC and the DC passes a challenge back to the caller. Arguments: PrimaryName -- Supplies the name of the DC we wish to authenticate with. ComputerName -- Name of the machine making the call. ClientCredential -- 64 bit challenge supplied by the BDC or member server. ServerCredential -- Receives 64 bit challenge from the PDC. Return Value: The status of the operation. --*/ { #ifdef _WKSTA_NETLOGON return ERROR_NOT_SUPPORTED; UNREFERENCED_PARAMETER( PrimaryName ); UNREFERENCED_PARAMETER( ComputerName ); UNREFERENCED_PARAMETER( ClientChallenge ); UNREFERENCED_PARAMETER( ServerChallenge ); #endif // _WKSTA_NETLOGON #ifdef _DC_NETLOGON NTSTATUS Status; PDOMAIN_INFO DomainInfo = NULL; // // This API is not supported on workstations. // if ( NlGlobalMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( PrimaryName ); if ( DomainInfo == NULL ) { Status = STATUS_INVALID_COMPUTER_NAME; goto Cleanup; } NlPrint((NL_CHALLENGE_RES, "NetrServerReqChallenge: ClientChallenge = " )); NlpDumpBuffer(NL_CHALLENGE_RES, ClientChallenge, sizeof(*ClientChallenge) ); // // Compute ServerChallenge to pass back to requestor // NlComputeChallenge(ServerChallenge); NlPrint((NL_CHALLENGE_RES, "NetrServerReqChallenge: ServerChallenge = " )); NlpDumpBuffer(NL_CHALLENGE_RES, ServerChallenge, sizeof(*ServerChallenge) ); // // Add this entry into the challenge list. // // Remember both challenges until the corresponding I_NetAuthenticate call. // Notice that both challenges are not yet SessionKey-encrypted // Status = NlInsertChallenge( ComputerName, ClientChallenge, ServerChallenge ); // // Common exit point // Cleanup: // // If the request failed, be carefull to not leak authentication // information. // if ( !NT_SUCCESS(Status) ) { RtlZeroMemory( ServerChallenge, sizeof(*ServerChallenge) ); } if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } return Status; #endif // _DC_NETLOGON } NTSTATUS NetrServerAuthenticate3( IN LPWSTR PrimaryName OPTIONAL, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType, IN LPWSTR ComputerName, IN PNETLOGON_CREDENTIAL ClientCredential, OUT PNETLOGON_CREDENTIAL ServerCredential, IN OUT PULONG NegotiatedFlags, OUT PULONG AccountRid ) /*++ Routine Description: This is the server side of I_NetServerAuthenticate I_NetServerAuthenticate is the second of two functions used by a client Netlogon service to authenticate with another Netlogon service. (See I_NetServerReqChallenge above.) Both a SAM or UAS server authenticates using this function. This function passes a credential to the DC and the DC passes a credential back to the caller. Arguments: PrimaryName -- Supplies the name of the DC we wish to authenticate with. AccountName -- Name of the Account to authenticate with. SecureChannelType -- The type of the account being accessed. This field must be set to UasServerSecureChannel to indicate a call from downlevel (LanMan 2.x and below) BDC or member server. ComputerName -- Name of the BDC or member server making the call. ClientCredential -- 64 bit credential supplied by the BDC or member server. ServerCredential -- Receives 64 bit credential from the PDC. NegotiatedFlags -- Specifies flags indicating what features the BDC supports. Returns a subset of those flags indicating what features the PDC supports. The PDC/BDC should ignore any bits that it doesn't understand. AccountRid -- Returns the RID of the account identified by AccountName Return Value: The status of the operation. --*/ { #ifdef _WKSTA_NETLOGON return ERROR_NOT_SUPPORTED; UNREFERENCED_PARAMETER( PrimaryName ); UNREFERENCED_PARAMETER( AccountName ); UNREFERENCED_PARAMETER( SecureChannelType ); UNREFERENCED_PARAMETER( ComputerName ); UNREFERENCED_PARAMETER( ClientCredential ); UNREFERENCED_PARAMETER( ServerCredential ); UNREFERENCED_PARAMETER( NegotiatedFlags ); #endif // _WKSTA_NETLOGON #ifdef _DC_NETLOGON NTSTATUS Status = STATUS_SUCCESS; PDOMAIN_INFO DomainInfo = NULL; ULONG LoopCount; NETLOGON_CREDENTIAL LocalClientCredential; NETLOGON_SESSION_KEY SessionKey; NT_OWF_PASSWORD OwfPassword; NT_OWF_PASSWORD OwfPreviousPassword; NT_OWF_PASSWORD LocalOwfPassword; NETLOGON_CREDENTIAL ServerChallenge; NETLOGON_CREDENTIAL ClientChallenge; BOOL IsInterdomainTrustAccount = FALSE; ULONG TrustAttributes; BOOL ClientAuthenticated = FALSE; PLIST_ENTRY ChallengeEntry; PNL_CHALLENGE Challenge; ULONG ChallengeCount = 0; // // This API is not supported on workstations. // if ( NlGlobalMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // Start the WMI trace of server authentication // NlpTraceServerAuthEvent( EVENT_TRACE_TYPE_START, ComputerName, AccountName, SecureChannelType, NegotiatedFlags, STATUS_SUCCESS ); // Status isn't used at start // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( PrimaryName ); NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerAuthenticate entered: %ws on account %ws (Negot: %lx)\n", ComputerName, AccountName, *NegotiatedFlags )); if ( DomainInfo == NULL ) { Status = STATUS_INVALID_COMPUTER_NAME; goto Cleanup; } // // Disallow this function for Lanman 2.X servers. // if ( SecureChannelType == UasServerSecureChannel ) { NlPrint((NL_CRITICAL,"NetrServerAuthenticate " "from LM 2.x (disallowed).\n")); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Compute the NegotiatedFlags both sides support // *NegotiatedFlags &= NETLOGON_SUPPORTS_MASK | NETLOGON_SUPPORTS_DNS_DOMAIN_TRUST | NETLOGON_SUPPORTS_STRONG_KEY | NETLOGON_SUPPORTS_NT4EMULATOR_NEUTRALIZER | #ifdef ENABLE_AUTH_RPC (NlGlobalServerSupportsAuthRpc ? (NETLOGON_SUPPORTS_AUTH_RPC|NETLOGON_SUPPORTS_LSA_AUTH_RPC) : 0 ) | #endif // ENABLE_AUTH_RPC (NlGlobalParameters.AvoidSamRepl ? NETLOGON_SUPPORTS_AVOID_SAM_REPL : 0) | (NlGlobalParameters.AvoidLsaRepl ? NETLOGON_SUPPORTS_AVOID_LSA_REPL : 0); // // If we are emulating NT4.0 domain and the client // didn't indicate to neutralize the emulation, // treat the client as NT4.0 client. That way we // won't leak NT5.0 specific info to the client. // In fact, the client won't even ask for NT5.0 // specific info after receiving such negotiated // from us. // if ( NlGlobalParameters.Nt4Emulator && ((*NegotiatedFlags) & NETLOGON_SUPPORTS_NT4EMULATOR_NEUTRALIZER) == 0 ) { // // Pick up only those flags which existed in NT4.0 // *NegotiatedFlags &= NETLOGON_SUPPORTS_NT4_MASK; } // // Get the password for the account. For interdomain trust // trust account, get both current and previous passwords // if ( IsDomainSecureChannelType( SecureChannelType ) ) { IsInterdomainTrustAccount = TRUE; } Status = NlGetIncomingPassword( DomainInfo, AccountName, SecureChannelType, 0, // Let routine figure out bits from SecureChannelType TRUE, // Fail for disabled accounts &OwfPassword, IsInterdomainTrustAccount ? &OwfPreviousPassword : // Get previous password for interdomain account NULL, AccountRid, &TrustAttributes, NULL ); // Don't need the account type if ( !NT_SUCCESS(Status) ) { NlPrintDom(( NL_CRITICAL, DomainInfo, "NetrServerAuthenticate: Can't NlGetIncomingPassword for %ws 0x%lx.\n", AccountName, Status )); goto Cleanup; } // // Output the passwords as needed // NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: Password for account %ws = ", AccountName )); NlpDumpBuffer(NL_CHALLENGE_RES, &OwfPassword, sizeof(OwfPassword) ); if ( IsInterdomainTrustAccount ) { NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: Previous Password for account %ws = ", AccountName )); NlpDumpBuffer(NL_CHALLENGE_RES, &OwfPreviousPassword, sizeof(OwfPreviousPassword) ); } // // Loop through all challenge entries for this client // and try to authenticate it against any one of the challenges. // EnterCriticalSection( &NlGlobalChallengeCritSect ); // // First, take this opportunity to clean up expired challenge entries // NlScavengeOldChallenges(); // // Now try all challenges for this client // for ( ChallengeEntry = NlGlobalChallengeList.Flink; ChallengeEntry != &NlGlobalChallengeList; ChallengeEntry = ChallengeEntry->Flink ) { Challenge = CONTAINING_RECORD( ChallengeEntry, NL_CHALLENGE, ChNext ); // // Skip entries which are not for this client // if ( NlNameCompare(ComputerName, Challenge->ChClientName, NAMETYPE_COMPUTER) != 0 ) { continue; } ChallengeCount ++; // // Grab a copy of the Client and Server challenges. // RtlCopyMemory( &ServerChallenge, &Challenge->ChServerChallenge, sizeof(ServerChallenge) ); RtlCopyMemory( &ClientChallenge, &Challenge->ChClientChallenge, sizeof(ClientChallenge) ); NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ClientChallenge %lu = ", ChallengeCount )); NlpDumpBuffer(NL_CHALLENGE_RES, &ClientChallenge, sizeof(ClientChallenge) ); NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ServerChallenge %lu = ", ChallengeCount )); NlpDumpBuffer(NL_CHALLENGE_RES, &ServerChallenge, sizeof(ServerChallenge) ); // // Loop trying the local current password, then the local previous password // provided this is an interdomain trust account. // for ( LoopCount=0; LoopCount<2; LoopCount++ ) { // // On the first iteration, use the current password // if ( LoopCount == 0 ) { LocalOwfPassword = OwfPassword; // // On the second iteration, if this is an interdomain trust account, // use the previous password // } else if ( LoopCount == 1 && IsInterdomainTrustAccount ) { LocalOwfPassword = OwfPreviousPassword; // // Otherwise, try the next challenge, if any // } else { break; } // // Compute the session key given the two challenges and the // password. // Status = NlMakeSessionKey( *NegotiatedFlags, &LocalOwfPassword, &ClientChallenge, &ServerChallenge, &SessionKey ); if (!NT_SUCCESS(Status)) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerAuthenticate: Can't NlMakeSessionKey for %ws 0x%lx.\n", AccountName, Status )); LeaveCriticalSection( &NlGlobalChallengeCritSect ); goto Cleanup; } NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: SessionKey %lu = ", LoopCount )); NlpDumpBuffer(NL_CHALLENGE_RES, &SessionKey, sizeof(SessionKey) ); // // Compute ClientCredential to verify the one supplied by ComputerName // NlComputeCredentials( &ClientChallenge, &LocalClientCredential, &SessionKey); NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ClientCredential %lu GOT = ", LoopCount )); NlpDumpBuffer(NL_CHALLENGE_RES, ClientCredential, sizeof(*ClientCredential) ); NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ClientCredential %lu MADE = ", LoopCount )); NlpDumpBuffer(NL_CHALLENGE_RES, &LocalClientCredential, sizeof(LocalClientCredential) ); // // Verify the computed credentials with those supplied // if( RtlEqualMemory( ClientCredential, &LocalClientCredential, sizeof(LocalClientCredential)) ) { ClientAuthenticated = TRUE; break; } NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerAuthenticate: Bad password %lu for %ws on account %ws\n", LoopCount, ComputerName, AccountName )); } if ( ClientAuthenticated ) { break; } // // This challenge entry failed to authenticate. // Remember the account name as specified by the // client in this challenge entry if this account // isn't already on the entry. // if ( Challenge->ChFailedAccountName == NULL || wcsstr(Challenge->ChFailedAccountName, AccountName) == NULL ) { ULONG OldLength = 0; LPWSTR TmpStorage = NULL; // // If there is already an account name, // allocate space for it // if ( Challenge->ChFailedAccountName != NULL ) { // add storage for a comma and a space OldLength = wcslen( Challenge->ChFailedAccountName ) + 2; } // // Allocate space for old (if any) and new account names // TmpStorage = LocalAlloc( LMEM_ZEROINIT, (OldLength+wcslen(AccountName)+1)*sizeof(WCHAR) ); if ( TmpStorage != NULL ) { // // Copy old name(s), if any. // Separate names with a comma and a space. // if ( OldLength > 0 ) { RtlCopyMemory( TmpStorage, Challenge->ChFailedAccountName, (OldLength-2)*sizeof(WCHAR) ); wcscat( TmpStorage, L", "); } // // Append the new account name // wcscat(TmpStorage, AccountName); // // Free the old name(s) and keep the new one // if ( Challenge->ChFailedAccountName != NULL ) { LocalFree( Challenge->ChFailedAccountName ); } Challenge->ChFailedAccountName = TmpStorage; } } } LeaveCriticalSection( &NlGlobalChallengeCritSect ); // // Error out if we didn't authenticate the client // if ( !ClientAuthenticated ) { NlPrintDom(( NL_CRITICAL, DomainInfo, "NetrServerAuthenticate: Failed to authenticate %ws on account %ws\n", ComputerName, AccountName )); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Cleanup all challenges for this client // NlRemoveChallenge( ComputerName, AccountName, IsInterdomainTrustAccount ); // // Create the server session for this client // Status = NlInsertServerSession( DomainInfo, ComputerName, AccountName, SecureChannelType, // Only replicate those databases that negotiation says to replicate SS_AUTHENTICATED | NlMaxReplMask(*NegotiatedFlags) | ((TrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) ? SS_FOREST_TRANSITIVE : 0 ), *AccountRid, *NegotiatedFlags, (SecureChannelType == ServerSecureChannel) ? NlTransportLookup( ComputerName ) : NULL, &SessionKey, &LocalClientCredential ); if ( !NT_SUCCESS(Status) ) { NlPrintDom(( NL_CRITICAL, DomainInfo, "NetrServerAuthenticate: NlInsertServerSession failed for %ws on account %ws\n", ComputerName, AccountName )); goto Cleanup; } // // Compute ServerCredential from ServerChallenge to be returned to caller // NlComputeCredentials( &ServerChallenge, ServerCredential, &SessionKey ); NlPrint((NL_CHALLENGE_RES,"NetrServerAuthenticate: ServerCredential SEND = " )); NlpDumpBuffer(NL_CHALLENGE_RES, ServerCredential, sizeof(*ServerCredential) ); // // If the client is a pre NT 5 member workstation or BDC, // update the DS. // if ( !IsInterdomainTrustAccount && ((*NegotiatedFlags) & ~NETLOGON_SUPPORTS_NT4_MASK) == 0 ) { OSVERSIONINFOEXW OsVersionInfoEx; // // Build the OsVersionInfo structure. // RtlZeroMemory( &OsVersionInfoEx, sizeof(OsVersionInfoEx) ); OsVersionInfoEx.dwOSVersionInfoSize = sizeof(OsVersionInfoEx); // // Differentiate between NT 3 and NT 4. // if ( *NegotiatedFlags == 0 ) { OsVersionInfoEx.dwMajorVersion = 3; OsVersionInfoEx.dwMinorVersion = 1; } else if ( ((*NegotiatedFlags) & ~NETLOGON_SUPPORTS_NT351_MASK) == 0 ) { OsVersionInfoEx.dwMajorVersion = 3; OsVersionInfoEx.dwMinorVersion = 5; } else { OsVersionInfoEx.dwMajorVersion = 4; } NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerAuthenticate: %ws is running NT %ld.%ld\n", ComputerName, OsVersionInfoEx.dwMajorVersion, OsVersionInfoEx.dwMinorVersion )); // // Set the DnsHostName on the computer object. // Status = LsaISetClientDnsHostName( ComputerName, NULL, // No DnsHostName &OsVersionInfoEx, L"Windows NT", NULL ); // Not interested in returning DnsHostName if ( !NT_SUCCESS(Status) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerAuthenticate: Cannot set client DNS host name %lx (ignoring)\n", Status )); // This isn't fatal } } // // Success!!! // Status = STATUS_SUCCESS; NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerAuthenticate returns Success: %ws on account %ws (Negot: %lx)\n", ComputerName, AccountName, *NegotiatedFlags )); // // Common exit point // Cleanup: // // Return more appropriate error // if ( Status == STATUS_NO_SUCH_USER ) { Status = STATUS_NO_TRUST_SAM_ACCOUNT; } // // Handle failure // if ( !NT_SUCCESS( Status ) ) { LPWSTR MsgStrings[3]; // // Be careful to not leak authentication information. // RtlZeroMemory( ServerCredential, sizeof(*ServerCredential) ); *AccountRid = 0; // // Write event log as appropriate // MsgStrings[0] = ComputerName; MsgStrings[1] = AccountName; if (Status == STATUS_NO_TRUST_SAM_ACCOUNT) { NlpWriteEventlog( NELOG_NetlogonServerAuthNoTrustSamAccount, EVENTLOG_ERROR_TYPE, (LPBYTE) & Status, sizeof(Status), MsgStrings, 2 ); // // If this attempt failed with access denied and we tried challenges for // this client, the event log has already been output as appropriate // } else if ( !(Status == STATUS_ACCESS_DENIED && ChallengeCount > 0) ) { MsgStrings[2] = (LPWSTR) LongToPtr( Status ); NlpWriteEventlog( NELOG_NetlogonServerAuthFailed, EVENTLOG_ERROR_TYPE, (LPBYTE) & Status, sizeof(Status), MsgStrings, 3 | NETP_LAST_MESSAGE_IS_NTSTATUS ); } } if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } // // End the WMI trace of server authentication // NlpTraceServerAuthEvent( EVENT_TRACE_TYPE_END, ComputerName, AccountName, SecureChannelType, NegotiatedFlags, Status ); return Status; #endif // _DC_NETLOGON } NTSTATUS NetrServerAuthenticate2( IN LPWSTR PrimaryName OPTIONAL, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType, IN LPWSTR ComputerName, IN PNETLOGON_CREDENTIAL ClientCredential, OUT PNETLOGON_CREDENTIAL ServerCredential, IN OUT PULONG NegotiatedFlags ) /*++ Routine Description: This is the NT 3.5x and NT 4.x version of I_NetServerAuthenicate3. I_NetServerAuthenticate3 was introduced in NT 5.0 (December 1996). Arguments: Return Value: The status of the operation. --*/ { ULONG AccountRid; return NetrServerAuthenticate3( PrimaryName, AccountName, SecureChannelType, ComputerName, ClientCredential, ServerCredential, NegotiatedFlags, &AccountRid ); } NTSTATUS NetrServerAuthenticate( IN LPWSTR PrimaryName OPTIONAL, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE SecureChannelType, IN LPWSTR ComputerName, IN PNETLOGON_CREDENTIAL ClientCredential, OUT PNETLOGON_CREDENTIAL ServerCredential ) /*++ Routine Description: This is the NT 3.1 version of I_NetServerAuthenicate2. I_NetServerAuthenticate2 was introduced in NT 3.5 (December 1993). Arguments: Return Value: The status of the operation. --*/ { ULONG NegotiatedFlags = 0; return NetrServerAuthenticate2( PrimaryName, AccountName, SecureChannelType, ComputerName, ClientCredential, ServerCredential, &NegotiatedFlags ); } NTSTATUS NetpServerPasswordSet( IN LPWSTR PrimaryName OPTIONAL, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE AccountType, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN PENCRYPTED_LM_OWF_PASSWORD UasNewPassword OPTIONAL, IN PNL_TRUST_PASSWORD ClearNewPassword OPTIONAL ) /*++ Routine Description: This function is used to change the password for the account being used to maintain a secure channel. This function can only be called by a server which has previously authenticated with a DC by calling I_NetServerAuthenticate. The call is made depending on the account type: * A domain account password is changed from the PDC in the trusting domain. The I_NetServerPasswordSet call is made to any DC in the trusted domain. * A server account password is changed from the specific server. The I_NetServerPasswordSet call is made to the PDC in the domain the server belongs to. * A workstation account password is changed from the specific workstation. The I_NetServerPasswordSet call is made to a DC in the domain the server belongs to. This function uses RPC to contact the DC named by PrimaryName. Arguments: PrimaryName -- Name of the PDC to change the servers password with. NULL indicates this call is a local call being made on behalf of a UAS server by the XACT server. AccountName -- Name of the account to change the password for. AccountType -- The type of account being accessed. ComputerName -- Name of the BDC or member making the call. Authenticator -- supplied by the server. ReturnAuthenticator -- Receives an authenticator returned by the PDC. UasNewPassword -- The new OWF password for the server. ClearNewPassword -- The new cleartext password for the server. Either, UasNewPassword or ClearNewPassword will be NULL. Return Value: NT status code. STATUS_WRONG_PASSWORD - Indicates the server refuses to allow the password to be changed. The client should continue to use the prior password. --*/ { #ifdef _WKSTA_NETLOGON return ERROR_NOT_SUPPORTED; UNREFERENCED_PARAMETER( PrimaryName ); UNREFERENCED_PARAMETER( AccountName ); UNREFERENCED_PARAMETER( AccountType ); UNREFERENCED_PARAMETER( ComputerName ); UNREFERENCED_PARAMETER( Authenticator ); UNREFERENCED_PARAMETER( ReturnAuthenticator ); UNREFERENCED_PARAMETER( UasNewPassword ); #endif // _WKSTA_NETLOGON #ifdef _DC_NETLOGON NTSTATUS Status; PDOMAIN_INFO DomainInfo = NULL; PSERVER_SESSION ServerSession; SESSION_INFO SessionInfo; LM_OWF_PASSWORD OwfPassword; UNICODE_STRING NewPassword; DWORD ClearVersionNumber = 0; // // This API is not supported on workstations. // if ( NlGlobalMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // If the DS is recovering from a backup, // avoid changing the DS. // if ( NlGlobalDsPaused ) { NlPrint((NL_CRITICAL, "NetrServerPasswordSet: DsIsPaused.\n")); Status = STATUS_DS_BUSY; goto Cleanup; } // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( PrimaryName ); NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerPasswordSet: Comp=%ws Acc=%ws Entered\n", ComputerName, AccountName )); if ( DomainInfo == NULL ) { Status = STATUS_INVALID_COMPUTER_NAME; goto Cleanup; } // // Get the Session key for this session. // LOCK_SERVER_SESSION_TABLE( DomainInfo ); ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; goto Cleanup; } SessionInfo.SessionKey = ServerSession->SsSessionKey; SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags; // // now verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); goto Cleanup; } // // Check if we're refusing password changes // // Only refuse password changes if the client is a workstation and the // client supports password changing. // // If this is a PDC and the request was passed-through a BDC, // we don't have access to the NETLOGON_SUPPORTS flag of the workstation. // As such, we'll simply not check the NETLOGON_SUPPORTS flag in that // case and assume the client can handle it. // if ( NlGlobalParameters.RefusePasswordChange && AccountType == WorkstationSecureChannel && (ServerSession->SsSecureChannelType == ServerSecureChannel || (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_REFUSE_CHANGE_PWD) != 0 )){ UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_WRONG_PASSWORD; goto Cleanup; } UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); // // If the caller passed a cleartext password, // decrypt it. // if ( ClearNewPassword != NULL ) { NL_TRUST_PASSWORD LocalClearNewPassword; NL_PASSWORD_VERSION PasswordVersion; // // Simply decrypt using the session key // LocalClearNewPassword = *ClearNewPassword; NlDecryptRC4( &LocalClearNewPassword, sizeof(LocalClearNewPassword), &SessionInfo ); // // Sanity check the length. // if ( IsDomainSecureChannelType( AccountType )) { if ( (LocalClearNewPassword.Length >= sizeof(LocalClearNewPassword.Buffer)-sizeof(PasswordVersion)) || (LocalClearNewPassword.Length % sizeof(WCHAR)) != 0 ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerPasswordSet: Decrypted interdomain password is too long %ld\n", LocalClearNewPassword.Length )); Status = STATUS_ACCESS_DENIED; goto Cleanup; } } else { if ( (LocalClearNewPassword.Length >= sizeof(LocalClearNewPassword.Buffer)) || (LocalClearNewPassword.Length % sizeof(WCHAR)) != 0 ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerPasswordSet: Decrypted password is too long %ld\n", LocalClearNewPassword.Length )); Status = STATUS_ACCESS_DENIED; goto Cleanup; } } // // Convert the new password into a unicode string. // NewPassword.Buffer = (LPWSTR)(((LPBYTE)LocalClearNewPassword.Buffer) + NL_MAX_PASSWORD_LENGTH * sizeof(WCHAR) - LocalClearNewPassword.Length); ; NewPassword.MaximumLength = NewPassword.Length = (USHORT)LocalClearNewPassword.Length; // // Get the password version number for an interdomain trust // account (may be absent) // if ( IsDomainSecureChannelType( AccountType ) ) { RtlCopyMemory( &PasswordVersion, ((LPBYTE)LocalClearNewPassword.Buffer) + NL_MAX_PASSWORD_LENGTH * sizeof(WCHAR) - LocalClearNewPassword.Length - sizeof(PasswordVersion), sizeof(PasswordVersion) ); if ( PasswordVersion.PasswordVersionPresent == PASSWORD_VERSION_NUMBER_PRESENT && PasswordVersion.PasswordVersionNumber > 0 ) { ClearVersionNumber = PasswordVersion.PasswordVersionNumber; NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerPasswordSet: Got password version number 0x%lx\n", ClearVersionNumber )); } else { NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerPasswordSet: Got no password version number\n" )); } } // // If the caller passed an OWF password, // decrypt it. // } else if ( UasNewPassword != NULL ) { // // decrypt the sessionkey from password // i.e. OwfPassword = D2((E2(E1(STD_TXT, PW), SK)), SK) // = E1(STD_TXT, PW) // OwfPassword = One Way Function of the cleartext password. // if (Status = RtlDecryptLmOwfPwdWithLmOwfPwd( UasNewPassword, (PLM_OWF_PASSWORD) &SessionInfo.SessionKey, &OwfPassword )) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); goto Cleanup; } // // Internal error } else { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerPasswordSet: Neither clear nor OWF password.\n")); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Do the request locally. // NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerPasswordSet: Comp=%ws Acc=%ws Changing password locally\n", ComputerName, AccountName )); // // Set the password on the account. // Status = NlSetIncomingPassword( DomainInfo, AccountName, AccountType, ClearNewPassword == NULL ? NULL : &NewPassword, ClearVersionNumber, ClearNewPassword == NULL ? &OwfPassword : NULL ); if ( !NT_SUCCESS(Status) ) { goto Cleanup; } Status = STATUS_SUCCESS; // // Common exit point // Cleanup: // // If the request failed, be carefull to not leak authentication // information. // if ( Status == STATUS_ACCESS_DENIED ) { RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) ); } // // Also zero out automatic variables which store passwords to avoid // having these on the stack for indefinite time. // RtlZeroMemory( &OwfPassword, sizeof(OwfPassword) ); RtlZeroMemory( &NewPassword, sizeof(NewPassword) ); NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerPasswordSet: Comp=%ws Acc=%ws returns 0x%lX\n", ComputerName, AccountName, Status )); if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } return Status; #endif // _DC_NETLOGON } NTSTATUS NetrServerPasswordGet( IN LPWSTR PrimaryName, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE AccountType, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedNtOwfPassword ) /*++ Routine Description: This function is used to by a BDC to get a machine account password from the PDC in the doamin. This function can only be called by a server which has previously authenticated with a DC by calling I_NetServerAuthenticate. This function uses RPC to contact the DC named by PrimaryName. Arguments: PrimaryName -- Computer name of the PDC to remote the call to. AccountName -- Name of the account to get the password for. AccountType -- The type of account being accessed. ComputerName -- Name of the BDC making the call. Authenticator -- supplied by the server. ReturnAuthenticator -- Receives an authenticator returned by the PDC. EncryptedNtOwfPassword -- Returns the OWF password of the account. Return Value: NT status code. --*/ { NTSTATUS Status; PDOMAIN_INFO DomainInfo = NULL; PSERVER_SESSION ServerSession; SESSION_INFO SessionInfo; NT_OWF_PASSWORD OwfPassword; // // This API is not supported on workstations. // if ( NlGlobalMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( PrimaryName ); NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerPasswordGet: Comp=%ws Acc=%ws Entered\n", ComputerName, AccountName )); if ( DomainInfo == NULL ) { Status = STATUS_INVALID_COMPUTER_NAME; goto Cleanup; } // // This call is only allowed to a PDC. // if ( DomainInfo->DomRole != RolePrimary ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerPasswordGet: Call only valid to a PDC.\n" )); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Get the Session key for this session. // LOCK_SERVER_SESSION_TABLE( DomainInfo ); ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; goto Cleanup; } SessionInfo.SessionKey = ServerSession->SsSessionKey; // SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags; // // now verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); goto Cleanup; } // // Call is only allowed from a BDC. // if ( ServerSession->SsSecureChannelType != ServerSecureChannel ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerPasswordGet: Call only valid from a BDC.\n" )); UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; goto Cleanup; } UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); // // Get the password for the account. // Status = NlGetIncomingPassword( DomainInfo, AccountName, AccountType, 0, // Let routine compute from AccountType TRUE, // Fail if account is disabled &OwfPassword, NULL, // Don't return the previous password NULL, // Don't return the account RID NULL, // Don't return the trust attributes NULL ); // Don't need the account type if ( !NT_SUCCESS(Status) ) { goto Cleanup; } // // Encrypt the password again with the session key. // The BDC will decrypt it on the other side. // Status = RtlEncryptNtOwfPwdWithNtOwfPwd( &OwfPassword, (PNT_OWF_PASSWORD) &SessionInfo.SessionKey, EncryptedNtOwfPassword) ; if ( !NT_SUCCESS( Status )) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerPasswordGet: Cannot RtlEncryptNtOwfPwdWithNtOwfPwd %lX\n", Status)); goto Cleanup; } Status = STATUS_SUCCESS; // // Common exit point // Cleanup: // // If the request failed, be carefull to not leak authentication // information. // if ( Status == STATUS_ACCESS_DENIED ) { RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) ); RtlZeroMemory( EncryptedNtOwfPassword, sizeof(*EncryptedNtOwfPassword) ); } NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrServerPasswordGet: Comp=%ws Acc=%ws returns 0x%lX\n", ComputerName, AccountName, Status )); if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } return Status; } NTSTATUS NetrServerGetTrustInfo( IN LPWSTR TrustedDcName, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE AccountType, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedNewOwfPassword, OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedOldOwfPassword, OUT PNL_GENERIC_RPC_DATA *TrustInfo ) /*++ Routine Description: This function is used by a trusting side DC/workstation to get the trust info (new and old passwords and trust attributes) from the trusted side. The account name requested must match the account name used at the secure channel setup time unless the call is made by a BDC to its PDC; the BDC has full access to the entire trust info. This function can only be called by a server which has previously authenticated with a DC by calling I_NetServerAuthenticate. This function uses RPC to contact the DC named by TrustedDcName. Arguments: TrustedDcName -- Computer name of the DC to remote the call to. AccountName -- Name of the account to get the password for. AccountType -- The type of account being accessed. ComputerName -- Name of the DC making the call. Authenticator -- supplied by this server. ReturnAuthenticator -- Receives an authenticator returned by the trusted side DC. EncryptedNewOwfPassword -- Returns the new OWF password of the account. EncryptedOldOwfPassword -- Returns the old OWF password of the account. TrustInfo -- Returns trust info data (currently trust attributes). Must be freed by calling NetApiBufferFree. Return Value: NT status code. --*/ { NTSTATUS Status; PDOMAIN_INFO DomainInfo = NULL; PSERVER_SESSION ServerSession; SESSION_INFO SessionInfo; NT_OWF_PASSWORD NewOwfPassword; NT_OWF_PASSWORD OldOwfPassword; ULONG AccountRid; ULONG TrustAttributes = 0; ULONG ServerSessionAccountRid; BOOLEAN VerifyAccountMatch = FALSE; BOOLEAN GetBothPasswords = FALSE; PNL_GENERIC_RPC_DATA LocalTrustInfo = NULL; // // This API is not supported on workstations. // if ( NlGlobalMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( TrustedDcName ); if ( DomainInfo == NULL ) { Status = STATUS_INVALID_COMPUTER_NAME; goto Cleanup; } // // Get the Session key for this session. // LOCK_SERVER_SESSION_TABLE( DomainInfo ); ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; goto Cleanup; } SessionInfo.SessionKey = ServerSession->SsSessionKey; // SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags; // // now verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); goto Cleanup; } // // Check if we need to verify whether the trusted side // is allowed to get passwords for this particular account. // For our BDC, we allow full access to trust info. // if ( ServerSession->SsSecureChannelType != ServerSecureChannel ) { ServerSessionAccountRid = ServerSession->SsAccountRid; VerifyAccountMatch = TRUE; } // // See if we need to get both new and previous passwords // if ( IsDomainSecureChannelType( AccountType ) ) { GetBothPasswords = TRUE; } UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); // // Get the password for the account. // Status = NlGetIncomingPassword( DomainInfo, AccountName, AccountType, 0, // Let routine compute from AccountType TRUE, // Fail if account is disabled &NewOwfPassword, GetBothPasswords ? &OldOwfPassword : NULL, &AccountRid, &TrustAttributes, // Get trust attributes NULL ); // Don't need the account type if ( !NT_SUCCESS(Status) ) { goto Cleanup; } // // See if we need to verify that the account requested is // the one for which this server session was created. // if ( VerifyAccountMatch && ServerSessionAccountRid != AccountRid ) { NlPrintDom(( NL_CRITICAL, DomainInfo, "NetrServerTrustPasswordsGet: %ws with AccountRid %lu asked for wrong account %ws and Rid %lu.\n", ComputerName, ServerSessionAccountRid, AccountName, AccountRid )); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Encrypt the passwords again with the session key. // The trusting side DC will decrypt it on the other side. // Status = RtlEncryptNtOwfPwdWithNtOwfPwd( &NewOwfPassword, (PNT_OWF_PASSWORD) &SessionInfo.SessionKey, EncryptedNewOwfPassword) ; if ( !NT_SUCCESS( Status )) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerTrustPasswordsGet: Cannot RtlEncryptNtOwfPwdWithNtOwfPwd 0x%lx\n", Status)); goto Cleanup; } // // If no password exists on the account, // return a blank password. // if ( !GetBothPasswords ) { UNICODE_STRING TempUnicodeString; RtlInitUnicodeString( &TempUnicodeString, NULL ); Status = RtlCalculateNtOwfPassword( &TempUnicodeString, &OldOwfPassword ); if ( !NT_SUCCESS(Status) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerTrustPasswordsGet: %ws: cannot RtlCalculateNtOwfPassword (NULL) 0x%lx\n", AccountName, Status )); goto Cleanup; } } Status = RtlEncryptNtOwfPwdWithNtOwfPwd( &OldOwfPassword, (PNT_OWF_PASSWORD) &SessionInfo.SessionKey, EncryptedOldOwfPassword) ; if ( !NT_SUCCESS( Status )) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrServerTrustPasswordsGet: Cannot RtlEncryptNtOwfPwdWithNtOwfPwd 0x%lx\n", Status)); goto Cleanup; } // // Return the trust attributes if requested. // Must be the first item on the list of // ULONGs returned. // if ( TrustInfo != NULL ) { NET_API_STATUS NetStatus; NetStatus = NetApiBufferAllocate( sizeof(NL_GENERIC_RPC_DATA)+sizeof(ULONG), &LocalTrustInfo ); if ( NetStatus != NO_ERROR ) { Status = STATUS_NO_MEMORY; goto Cleanup; } RtlZeroMemory( LocalTrustInfo, sizeof(NL_GENERIC_RPC_DATA)+sizeof(ULONG) ); LocalTrustInfo->UlongEntryCount = 1; LocalTrustInfo->UlongData = (PULONG)(LocalTrustInfo+1); *( (PULONG)(LocalTrustInfo+1) ) = TrustAttributes; *TrustInfo = LocalTrustInfo; } Status = STATUS_SUCCESS; // // Common exit point // Cleanup: // // If the request failed, be carefull to not leak authentication // information. // if ( !NT_SUCCESS( Status ) ) { RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) ); RtlZeroMemory( EncryptedNewOwfPassword, sizeof(*EncryptedNewOwfPassword) ); RtlZeroMemory( EncryptedOldOwfPassword, sizeof(*EncryptedOldOwfPassword) ); if ( LocalTrustInfo != NULL ) { NetApiBufferFree( LocalTrustInfo ); } } NlPrintDom((NL_MISC, DomainInfo, "NetrServerPasswordGet: Comp=%ws Acc=%ws returns 0x%lX\n", ComputerName, AccountName, Status )); if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } return Status; } NTSTATUS NetrServerTrustPasswordsGet( IN LPWSTR TrustedDcName, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE AccountType, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedNewOwfPassword, OUT PENCRYPTED_NT_OWF_PASSWORD EncryptedOldOwfPassword ) /*++ Routine Description: This function is used by a trusting side DC/workstation to get the new and old passwords from the trusted side. The account name requested must match the account name used at the secure channel setup time unless the call is made by a BDC to its PDC; the BDC has full access to the entire trust info. This function can only be called by a server which has previously authenticated with a DC by calling I_NetServerAuthenticate. This function uses RPC to contact the DC named by TrustedDcName. Arguments: TrustedDcName -- Computer name of the DC to remote the call to. AccountName -- Name of the account to get the password for. AccountType -- The type of account being accessed. ComputerName -- Name of the machine making the call. Authenticator -- supplied by the server making the call. ReturnAuthenticator -- Receives an authenticator returned by the trusted side DC. EncryptedNewOwfPassword -- Returns the new OWF password of the account. EncryptedOldOwfPassword -- Returns the old OWF password of the account. Return Value: NT status code. --*/ { return NetrServerGetTrustInfo( TrustedDcName, AccountName, AccountType, ComputerName, Authenticator, ReturnAuthenticator, EncryptedNewOwfPassword, EncryptedOldOwfPassword, NULL ); // no trust attributes } NTSTATUS NetrServerPasswordSet( IN LPWSTR PrimaryName OPTIONAL, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE AccountType, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN PENCRYPTED_LM_OWF_PASSWORD UasNewPassword ) /*++ Routine Description: See NetpServerPasswordSet. Arguments: See NetpServerPasswordSet. Return Value: See NetpServerPasswordSet. --*/ { return NetpServerPasswordSet( PrimaryName, AccountName, AccountType, ComputerName, Authenticator, ReturnAuthenticator, UasNewPassword, NULL ); // No clear password } NTSTATUS NetrServerPasswordSet2( IN LPWSTR PrimaryName OPTIONAL, IN LPWSTR AccountName, IN NETLOGON_SECURE_CHANNEL_TYPE AccountType, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN PNL_TRUST_PASSWORD ClearNewPassword ) /*++ Routine Description: See NetpServerPasswordSet. Arguments: See NetpServerPasswordSet. Return Value: See NetpServerPasswordSet. --*/ { return NetpServerPasswordSet( PrimaryName, AccountName, AccountType, ComputerName, Authenticator, ReturnAuthenticator, NULL, // No OWF password ClearNewPassword ); } NTSTATUS NlPackSerialNumber ( IN PLARGE_INTEGER SerialNumber, IN OUT PNETLOGON_DELTA_ENUM Delta, IN LPDWORD BufferSize, IN PSESSION_INFO SessionInfo ) /*++ Routine Description: Pack the specified serial number as a delta. Arguments: SerialNumber - The serial number to pack. Delta: pointer to the delta structure where the new delta will be returned. DBInfo: pointer to the database info structure. BufferSize: size of MIDL buffer that is consumed for this delta is returned here. SessionInfo: Info describing BDC that's calling us Return Value: NT status code. --*/ { PNLPR_MODIFIED_COUNT DeltaSerialNumberSkip; PSAMPR_USER_INFO_BUFFER UserAll = NULL; // // Only pack this delta if the BDC expects it. // NlAssert( SessionInfo->NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG); UNREFERENCED_PARAMETER(SessionInfo); NlPrint(( NL_SYNC_MORE, "Packing skip to serial number delta: %lx %lx\n", SerialNumber->HighPart, SerialNumber->LowPart )); *BufferSize = 0; Delta->DeltaType = SerialNumberSkip; Delta->DeltaID.Rid = 0; Delta->DeltaUnion.DeltaSerialNumberSkip = NULL; // // Allocate a buffer to return to the caller. // DeltaSerialNumberSkip = (PNLPR_MODIFIED_COUNT) MIDL_user_allocate( sizeof(*DeltaSerialNumberSkip) ); if (DeltaSerialNumberSkip == NULL) { return STATUS_NO_MEMORY; } *BufferSize += sizeof(*DeltaSerialNumberSkip); // // Copy the serial number into the buffer. // RtlCopyMemory( &DeltaSerialNumberSkip->ModifiedCount, SerialNumber, sizeof( DeltaSerialNumberSkip->ModifiedCount ) ); Delta->DeltaUnion.DeltaSerialNumberSkip = DeltaSerialNumberSkip; // // All Done // return STATUS_SUCCESS; } NTSTATUS NlPackSingleDelta ( IN PCHANGELOG_ENTRY ChangeLogEntry, IN OUT PNETLOGON_DELTA_ENUM_ARRAY DeltaArray, OUT LPDWORD BufferConsumed, IN PSESSION_INFO SessionInfo, IN BOOLEAN ReturnSerialNumberDeltas ) /*++ Routine Description: Pack the deltas for a single change log entry. Arguments: ChangeLogEntry - The Change Log Entry describing the account to pack. DeltaArray - Describes the array of deltas. The appropriate deltas will be added to the end of this array. The caller has guaranteed that that is room for at least MAX_DELTAS_PER_CHANGELOG - 1 deltas to be added to the array. BufferConsumed - returns the size of MIDL buffer that is consumed for the returned deltas SessionInfo: Info describing BDC that's calling us ReturnSerialNumberDeltas -- True if serial number deltas should be returned when needed. Return Value: STATUS_SUCCESS -- The function completed successfully. --*/ { NTSTATUS Status = STATUS_SUCCESS; PDB_INFO DBInfo; DWORD BufferSize; UNICODE_STRING UnicodeSecretName; LPWSTR AccountName; PSID Sid; // // Initialization // DBInfo = &NlGlobalDBInfoArray[ChangeLogEntry->DBIndex]; *BufferConsumed = 0; // // Macro to account for another delta array entry being consumed/returned // # define MoveToNextDeltaArrayEntry( _BufferSize ) \ *BufferConsumed += (sizeof(NETLOGON_DELTA_ENUM) + _BufferSize); \ (DeltaArray->CountReturned)++; // // Put the data for the changelog entry into the user's buffer. // switch ( ChangeLogEntry->DeltaType ) { case AddOrChangeDomain: Status = NlPackSamDomain( &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize ); break; // // The DS can't distinguish between a membership change and a property change. // always replicate all aspects of the group. // case AddOrChangeGroup: case ChangeGroupMembership: case RenameGroup: // // we treat the rename as three deltas. // 1. AddorChangeGroup delta. // Backup deletes the account with old name and creates // an account with new name. // // 2. Delta to tell the BDC that delta (3) below is for the // same serial number as delta (1) above. // // 3. ChangeGroupMembership delta. // Backup readds all members to new group. // Status = NlPackSamGroup( ChangeLogEntry->ObjectRid, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize ); if( !NT_SUCCESS( Status ) ) { break; } MoveToNextDeltaArrayEntry( BufferSize ); if ( ReturnSerialNumberDeltas ) { Status = NlPackSerialNumber( &ChangeLogEntry->SerialNumber, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), &BufferSize, SessionInfo ); if( !NT_SUCCESS( Status ) ) { break; } MoveToNextDeltaArrayEntry( BufferSize ); } Status = NlPackSamGroupMember( ChangeLogEntry->ObjectRid, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize ); break; case AddOrChangeUser: case RenameUser: Status = NlPackSamUser( ChangeLogEntry->ObjectRid, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize, SessionInfo ); break; // // The DS can't distinguish between a membership change and a property change. // always replicate all aspects of the alias. // case AddOrChangeAlias: case ChangeAliasMembership: case RenameAlias: // // we treat the rename as two deltas. // 1. AddorChangeAlias delta. // Backup deletes the account with old name and creates // an account with new name. // // 2. Delta to tell the BDC that delta (3) below is for the // same serial number as delta (1) above. // // 3. ChangeAliasMembership delta. // Backup readds all members to new alias. // Status = NlPackSamAlias( ChangeLogEntry->ObjectRid, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize ); if( !NT_SUCCESS( Status ) ) { break; } MoveToNextDeltaArrayEntry( BufferSize ); if ( ReturnSerialNumberDeltas ) { Status = NlPackSerialNumber( &ChangeLogEntry->SerialNumber, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), &BufferSize, SessionInfo ); if( !NT_SUCCESS( Status ) ) { break; } MoveToNextDeltaArrayEntry( BufferSize ); } Status = NlPackSamAliasMember( ChangeLogEntry->ObjectRid, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize ); break; case AddOrChangeLsaPolicy: Status = NlPackLsaPolicy( &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize ); break; case AddOrChangeLsaTDomain: NlAssert( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ); if( (ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED) == 0 ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; break; } Status = NlPackLsaTDomain( (PSID) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)), &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize ); break; case AddOrChangeLsaAccount: NlAssert( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ); if( (ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED) == 0 ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; break; } Status = NlPackLsaAccount( (PSID) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)), &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize, SessionInfo ); break; case AddOrChangeLsaSecret: NlAssert( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ); if( (ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; break; } RtlInitUnicodeString( &UnicodeSecretName, (LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY)) ); Status = NlPackLsaSecret( &UnicodeSecretName, &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize, SessionInfo ); break; case DeleteGroup: case DeleteGroupByName: case DeleteUser: case DeleteUserByName: // // If this is an NT 3.5 BDC, // send the account name upon account deletion. if ( ReturnSerialNumberDeltas ) { // // Send the NT 3.5 BDC a special delta type indicating the // Name is attached. // if ( ChangeLogEntry->DeltaType == DeleteGroup ) { (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType = DeleteGroupByName; } else if ( ChangeLogEntry->DeltaType == DeleteUser ) { (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType = DeleteUserByName; } else { (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType = ChangeLogEntry->DeltaType; } (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Rid = ChangeLogEntry->ObjectRid; // // Add the account name to the entry. // NlAssert(ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED); if( (ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; break; } BufferSize = (wcslen( (LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY))) + 1 ) * sizeof(WCHAR); AccountName = (LPWSTR) MIDL_user_allocate( BufferSize ); if (AccountName == NULL) { Status = STATUS_NO_MEMORY; break; } wcscpy( AccountName, (LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY))); (DeltaArray->Deltas)[DeltaArray->CountReturned]. DeltaUnion.DeltaDeleteGroup = MIDL_user_allocate(sizeof(struct _NETLOGON_DELTA_DELETE)); if ((DeltaArray->Deltas)[DeltaArray->CountReturned]. DeltaUnion.DeltaDeleteGroup == NULL ) { MIDL_user_free(AccountName); Status = STATUS_NO_MEMORY; break; } INIT_PLACE_HOLDER( (DeltaArray->Deltas)[DeltaArray->CountReturned]. DeltaUnion.DeltaDeleteGroup ); (DeltaArray->Deltas)[DeltaArray->CountReturned]. DeltaUnion.DeltaDeleteGroup->AccountName = AccountName; break; // out of switch } /* Drop through to handle NT 3.1 case. */ case DeleteAlias: (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType = ChangeLogEntry->DeltaType; (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Rid = ChangeLogEntry->ObjectRid; BufferSize = 0; break; case DeleteLsaTDomain: case DeleteLsaAccount: NlAssert( ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED ); if( (ChangeLogEntry->Flags & CHANGELOG_SID_SPECIFIED) == 0 ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; break; } BufferSize = RtlLengthSid( (PSID)((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY))); Sid = (PSID) MIDL_user_allocate( BufferSize ); if( Sid == NULL ) { Status = STATUS_NO_MEMORY; break; } Status = RtlCopySid ( BufferSize, Sid, (PSID) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY))); if( !NT_SUCCESS( Status ) ) { MIDL_user_free( Sid ); break; } (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType = ChangeLogEntry->DeltaType; (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Sid = Sid; break; case DeleteLsaSecret: NlAssert(ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED); if( (ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED) == 0 ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; break; } BufferSize = (wcslen( (LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY))) + 1 ) * sizeof(WCHAR); AccountName = (LPWSTR) MIDL_user_allocate( BufferSize ); if (AccountName == NULL) { Status = STATUS_NO_MEMORY; break; } wcscpy( AccountName, (LPWSTR) ((LPBYTE)ChangeLogEntry + sizeof(CHANGELOG_ENTRY))); (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaType = ChangeLogEntry->DeltaType; (DeltaArray->Deltas)[DeltaArray->CountReturned].DeltaID.Name = AccountName; break; default: NlPrint((NL_CRITICAL, "NlPackSingleDelta: Invalid delta type in change log\n")); Status = STATUS_SYNCHRONIZATION_REQUIRED; break; } if ( NT_SUCCESS(Status) ) { MoveToNextDeltaArrayEntry( BufferSize ); } return Status; #undef MoveToNextDeltaArrayEntry } NTSTATUS NetrDatabaseDeltas ( IN LPWSTR PrimaryName, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN DWORD DatabaseID, IN OUT PNLPR_MODIFIED_COUNT NlDomainModifiedCount, OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet, IN DWORD PreferredMaximumLength ) /*++ Routine Description: This function is used by a SAM BDC to request SAM-style account delta information from a SAM PDC. This function can only be called by a server which has previously authenticated with the PDC by calling I_NetServerAuthenticate. This function uses RPC to contact the Netlogon service on the PDC. This function returns a list of deltas. A delta describes an individual domain, user or group and all of the field values for that object. The PDC maintains a list of deltas not including all of the field values for that object. Rather, the PDC retrieves the field values from SAM and returns those values from this call. The PDC optimizes the data returned on this call by only returning the field values for a particular object once on a single invocation of this function. This optimizes the typical case where multiple deltas exist for a single object (e.g., an application modified many fields of the same user during a short period of time using different calls to the SAM service). Arguments: PrimaryName -- Name of the PDC to retrieve the deltas from. ComputerName -- Name of the BDC or member server making the call. Authenticator -- supplied by the server. ReturnAuthenticator -- Receives an authenticator returned by the PDC. DatabaseID -- Identifies the databse for which the deltas are requested. For SAM database the ID is 0, for Builtin Domain the ID is 1. Other databases may be defined later. NlDomainModifiedCount -- Specifies the DomainModifiedCount of the last delta retrieved by the server. Returns the DomainModifiedCount of the last delta returned from the PDC on this call. Deltas -- Receives a pointer to a buffer where the information is placed. The information returned is an array of NETLOGON_DELTA_ENUM structures. PreferredMaximumLength - Preferred maximum length of returned data (in 8-bit bytes). This is not a hard upper limit, but serves as a guide to the server. Due to data conversion between systems with different natural data sizes, the actual amount of data returned may be greater than this value. Return Value: STATUS_SUCCESS -- The function completed successfully. STATUS_SYNCHRONIZATION_REQUIRED -- The replicant is totally out of sync and should call I_NetDataSync to do a full synchronization with the PDC. STATUS_MORE_ENTRIES -- The replicant should call again to get more data. STATUS_ACCESS_DENIED -- The replicant should re-authenticate with the PDC. --*/ { NTSTATUS Status; PDOMAIN_INFO DomainInfo = NULL; PSERVER_SESSION ServerSession = NULL; PCHANGELOG_ENTRY ChangeLogEntry = NULL; BOOLEAN PackThisEntry = TRUE; BOOL ChangelogLocked = FALSE; PDB_INFO DBInfo; LARGE_INTEGER RunningSerialNumber; LARGE_INTEGER PackedSerialNumber; LARGE_INTEGER OriginalSerialNumber; DWORD BufferConsumed = 0; DWORD BufferSize = 0; PNETLOGON_DELTA_ENUM_ARRAY DeltaArray; SESSION_INFO SessionInfo; DEFSSIAPITIMER; INITSSIAPITIMER; STARTSSIAPITIMER; // // This API is not supported on workstations. // if ( NlGlobalMemberWorkstation || !NlGlobalPdcDoReplication ) { NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: called from %ws. This machine doesn't support replication.\n", ComputerName )); return STATUS_NOT_SUPPORTED; } // // If the DS is recovering from a backup, // avoid changing the DS. // if ( NlGlobalDsPaused ) { NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: DsIsPaused.\n")); // Don't return a new status code since NT 4 DC would do a full sync return STATUS_ACCESS_DENIED; } // // Gross hack because of RPC implementation. // // Rpc executes API calls in an I/O completion port thread. If this thread // goes CPU bound, then no other RPC will be allowed to start. Even worse, // there is only one outstanding listen, so the 'second' coming RPC call // gets RPC_S_SERVER_TOO_BUSY. // // By sleeping here (even for a short period) the I/O completion port releases // another thread since it thinks this thread went I/O bound. // // We've seen this thread go CPU bound doing a full sync of a database with // 1000's of LSA account objects. // RpcServerYield(); // // Initialization // if ( DatabaseID >= NUM_DBS ) { return STATUS_INVALID_LEVEL; } *DeltaArrayRet = DeltaArray = (PNETLOGON_DELTA_ENUM_ARRAY) MIDL_user_allocate( sizeof(NETLOGON_DELTA_ENUM_ARRAY) ); if( DeltaArray == NULL ) { return STATUS_NO_MEMORY; } DeltaArray->CountReturned = 0; DeltaArray->Deltas = NULL; SessionInfo.NegotiatedFlags = 0; DBInfo = &NlGlobalDBInfoArray[DatabaseID]; RtlCopyMemory( &RunningSerialNumber, &NlDomainModifiedCount->ModifiedCount, sizeof(RunningSerialNumber)); OriginalSerialNumber.QuadPart = RunningSerialNumber.QuadPart; PackedSerialNumber.QuadPart = RunningSerialNumber.QuadPart; // // Find the domain this API was made to. // DomainInfo = NlFindDomainByServerName( PrimaryName ); NlPrintDom((NL_SYNC, DomainInfo, "NetrDatabaseDeltas: " FORMAT_LPWSTR " partial sync called by " FORMAT_LPWSTR " SerialNumber:%lx %lx.\n", DBInfo->DBName, ComputerName, RunningSerialNumber.HighPart, RunningSerialNumber.LowPart )); if ( DomainInfo == NULL ) { Status = STATUS_INVALID_COMPUTER_NAME; goto Cleanup; } if ( !IsPrimaryDomain( DomainInfo )) { Status = STATUS_NOT_SUPPORTED; goto Cleanup; } // // Retrieve the requestor's entry to get sessionkey // LOCK_SERVER_SESSION_TABLE( DomainInfo ); ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: No server session.\n")); // Don't log this event since it happens in nature after a reboot // or after we scavenge the server session. goto CleanupNoEventlog; } // // Allow this call only on ServerSecureChannel. // if( ServerSession->SsSecureChannelType != ServerSecureChannel ) { // // If the only preblem is that this BDC hasn't authenticated, // silently ask it to authenticate. // if ( ServerSession->SsSecureChannelType == NullSecureChannel ) { NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: No authenticated server session.\n")); ServerSession = NULL; UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); // Don't log this event since it happens in nature after a reboot // or after we scavenge the server session. Status = STATUS_ACCESS_DENIED; goto CleanupNoEventlog; } else { NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: SecureChannel type isn't BDC. %ld\n", ServerSession->SsSecureChannelType )); ServerSession = NULL; UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; goto Cleanup; } } // // Verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: authentication failed.\n" )); ServerSession = NULL; goto Cleanup; } // // Prevent entry from being deleted, but drop the global lock. // // Beware of server with two concurrent calls outstanding // (must have rebooted.) // if (ServerSession->SsFlags & SS_LOCKED ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: Concurrent call detected.\n" )); Status = STATUS_ACCESS_DENIED; ServerSession = NULL; goto Cleanup; } ServerSession->SsFlags |= SS_LOCKED; SessionInfo.SessionKey = ServerSession->SsSessionKey; SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags; UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); // // If the BDC is in sync, // simply return. // LOCK_CHANGELOG(); ChangelogLocked = TRUE; if ( RunningSerialNumber.QuadPart == NlGlobalChangeLogDesc.SerialNumber[DatabaseID].QuadPart ) { Status = STATUS_SUCCESS; goto Cleanup; } // // Get a copy of the appropriate entry in the change_log. // Note that the record_id contains last record received by client. // if ((ChangeLogEntry = NlGetNextUniqueChangeLogEntry( &NlGlobalChangeLogDesc, RunningSerialNumber, DBInfo->DBIndex, NULL ))== NULL) { // // Handle the case where the BDC has more recent changes than we do. // // Just return our newest change log entry with the same promotion count. // The BDC will realize what's going on and un-do its newer changes. // // Only do this if our PromotionCount is greater than the BDCs. If // our promotion count is equal to that of the BDC, either our change log // has wrapped, or the BDC is royally confused. // // Don't be tempted to return a change log entry with an // older promotion count. We'd have no way of knowing which delta // to actually return to the caller. // if ( ((NlGlobalChangeLogDesc.SerialNumber[DatabaseID].HighPart & NlGlobalChangeLogPromotionMask) > (RunningSerialNumber.HighPart & NlGlobalChangeLogPromotionMask)) && (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_REDO) ) { ChangeLogEntry = NlFindPromotionChangeLogEntry( &NlGlobalChangeLogDesc, RunningSerialNumber, DBInfo->DBIndex ); // // Don't actually pack this change log entry. We've found it // so we can pack a "serial number" delta. But the BDC already // has this particular change. // PackThisEntry = FALSE; } if ( ChangeLogEntry == NULL ) { NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: " "delta not found in cache, returning full required.\n" )); Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } else { NlPrint((NL_SYNC, "NetrDatabaseDeltas: BDC more recent than PDC (recovering).\n" )); } } UNLOCK_CHANGELOG(); ChangelogLocked = FALSE; // // Allocate memory for delta buffer. // DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate( MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ); if( DeltaArray->Deltas == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } // // wipe off the buffer so that cleanup will not be in fault. // RtlZeroMemory( DeltaArray->Deltas, MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ); // // Loop packing deltas as long as there is room for more deltas // // In some cases we pack multiple deltas on the wire for one entry in the // change log, we want to ensure that all of these deltas are sent to // the BDC on a single call. // while ( DeltaArray->CountReturned + MAX_DELTAS_PER_CHANGELOG <= MAX_DELTA_COUNT ) { // // If the serial number of the delta being packed isn't the one // expected by the BDC, tell the BDC what the serial number is. // if ( ChangeLogEntry->SerialNumber.QuadPart != PackedSerialNumber.QuadPart + 1 ) { if ( SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG){ Status = NlPackSerialNumber( &ChangeLogEntry->SerialNumber, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), &BufferSize, &SessionInfo ); if( !NT_SUCCESS( Status ) ) { goto Cleanup; } BufferConsumed += BufferSize; DeltaArray->CountReturned ++; // // If we're not really going to pack the entry, // pretend that we already have. // if ( !PackThisEntry) { PackedSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart; } } } if ( PackThisEntry ) { // // Put the data for the changelog entry into the user's buffer. // Status = NlPackSingleDelta( ChangeLogEntry, DeltaArray, &BufferSize, &SessionInfo, (BOOLEAN)((SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_BDC_CHANGELOG) != 0) ); // // If we successfully put the delta into the delta array, // do the bookwork // if ( NT_SUCCESS( Status ) ) { BufferConsumed += BufferSize; PackedSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart; NlPrint((NL_SYNC_MORE, "NetrDatabaseDeltas: Modified count of the " "packed record: %lx %lx\n", ChangeLogEntry->SerialNumber.HighPart, ChangeLogEntry->SerialNumber.LowPart )); // // In the case where an user/group/alias record was // added and deleted before the delta was made we will // trace the change log and see there is correpondance // delete log. If we found one then ignore this delta // and proceed to the next delta. If we couldn't find // one then return error STATUS_SYNCHRONIZATION_REQUIRED. // } else if ( IsObjectNotFoundStatus( ChangeLogEntry->DeltaType, Status ) ) { if( !NlRecoverChangeLog(ChangeLogEntry) ) { NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: object not found in database, and no delete delta found (%lx).\n", Status )); #ifdef notdef Status = STATUS_SYNCHRONIZATION_REQUIRED; IF_NL_DEBUG( BREAKPOINT ) { NlAssert( FALSE ); } goto Cleanup; #else // notdef // // NT 5.0 SAM doesn't hold the write lock while determining if // the object exists. So, the object might have been deleted and // the but the delete delta hasn't been written to the change log yet. // So, assume that the delete delta will appear sooner or later. // // REVIEW: I could just pack a delete delta. // Status = STATUS_SUCCESS; #endif // notdef } else { // // We found a delete delta, so ignore the original delta. // Status = STATUS_SUCCESS; } // // All other errors are fatal // } else { goto Cleanup; } } PackThisEntry = TRUE; // // Free up used temp. record // RunningSerialNumber.QuadPart = ChangeLogEntry->SerialNumber.QuadPart; NetpMemoryFree(ChangeLogEntry); ChangeLogEntry = NULL; // // If we've returned all the entries, we're all done. // LOCK_CHANGELOG(); ChangelogLocked = TRUE; if ((ChangeLogEntry = NlGetNextUniqueChangeLogEntry( &NlGlobalChangeLogDesc, RunningSerialNumber, DBInfo->DBIndex, NULL )) == NULL) { Status = STATUS_SUCCESS; goto Cleanup; } UNLOCK_CHANGELOG(); ChangelogLocked = FALSE; // // Don't return more data to the caller than he wants. // if( BufferConsumed >= PreferredMaximumLength) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } // // If we're debugging replication, return only one change to the caller. // #if NETLOGONDBG if ( NlGlobalParameters.DbFlag & NL_ONECHANGE_REPL ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } #endif // NETLOGONDBG // // If the service is going down, stop packing deltas and // return to the caller. // if( NlGlobalTerminate ) { NlPrint((NL_CRITICAL, "NetrDatabaseDeltas is asked to return " "when the service is going down.\n")); Status = STATUS_MORE_ENTRIES; goto Cleanup; } } Status = STATUS_MORE_ENTRIES; Cleanup: // // write event log // if ( !NT_SUCCESS( Status ) ) { LPWSTR MsgStrings[2]; MsgStrings[0] = ComputerName; MsgStrings[1] = (LPWSTR) LongToPtr( Status ); NlpWriteEventlog( NELOG_NetlogonPartialSyncCallFailed, EVENTLOG_WARNING_TYPE, (LPBYTE)&Status, sizeof(Status), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NTSTATUS | NETP_ALLOW_DUPLICATE_EVENTS ); } else { // // Log the successful replication only if deltas have been returned // to the caller. // if ( DeltaArray->CountReturned != 0 ) { LPWSTR MsgStrings[2]; WCHAR CountBuffer[20]; // random size MsgStrings[0] = ComputerName; ultow( DeltaArray->CountReturned, CountBuffer, 10); MsgStrings[1] = CountBuffer; NlpWriteEventlog( NELOG_NetlogonPartialSyncCallSuccess, EVENTLOG_INFORMATION_TYPE, NULL, 0, MsgStrings, 2 | NETP_ALLOW_DUPLICATE_EVENTS ); } } // // Free up locally allocated resources. // CleanupNoEventlog: // // Copy the serial number back to the caller // if ( NT_SUCCESS(Status)) { RtlCopyMemory( &NlDomainModifiedCount->ModifiedCount, &PackedSerialNumber, sizeof(PackedSerialNumber)); // // If this is an NT 3.1 BDC, // Only remember the latest Serial Number it asked for, AND // force it the call back once it has updated the SerialNumber // so we know what that serial number is. // // NT 3.5 BDCs "persistently" try to update their database to the // PDCs version once they get a pulse indicating their database is // out of date. // if ( (SessionInfo.NegotiatedFlags & NETLOGON_SUPPORTS_PERSISTENT_BDC) == 0 ) { // // Use the SerialNumber the BDC originally passed us. // PackedSerialNumber.QuadPart = OriginalSerialNumber.QuadPart; // // If we're returning any deltas at all, // force the BDC to call us back. // if ( Status == STATUS_SUCCESS && DeltaArray->CountReturned != 0 ) { Status = STATUS_MORE_ENTRIES; } } // // If we weren't successful, // Don't return any deltas. // } else { if ( DeltaArray->Deltas != NULL ) { NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned ); DeltaArray->Deltas = NULL; } DeltaArray->CountReturned = 0; } if ( ChangelogLocked ) { UNLOCK_CHANGELOG(); } if( ChangeLogEntry != NULL) { NetpMemoryFree( ChangeLogEntry ); } // // Unlock the server session entry if we've locked it. // if ( ServerSession != NULL ) { // // If we are successfully returning these deltas to the BDC, // update our tables to reflect the changes. // if ( Status == STATUS_SUCCESS ) { NlPrimaryAnnouncementFinish( ServerSession, DatabaseID, &PackedSerialNumber ); } NlUnlockServerSession( ServerSession ); } // // If the BDC called us just as SAM was shutting down, // map the status to prevent the BDC from full syncing. // if ( Status == STATUS_INVALID_SERVER_STATE ) { Status = STATUS_ACCESS_DENIED; } NlPrint((NL_SYNC, "NetrDatabaseDeltas: " FORMAT_LPWSTR " returning (0x%lx) to " FORMAT_LPWSTR "\n", DBInfo->DBName, Status, ComputerName )); STOPSSIAPITIMER; if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } NlPrint((NL_REPL_TIME,"NetrDatabaseDeltas Time:\n")); PRINTSSIAPITIMER; return Status; } NTSTATUS NlSyncSamDatabase( IN PSERVER_SESSION ServerSession, IN DWORD DatabaseID, IN SYNC_STATE RestartState, IN OUT PULONG SyncContext, IN OUT PNETLOGON_DELTA_ENUM_ARRAY DeltaArray, IN DWORD PreferredMaximumLength, IN PSESSION_INFO SessionInfo ) /*++ Routine Description: This function is a real worker for the NetrDatabaseSync function and retrieves a SAM database in the delta buffer. This function uses the find-first find-next model to return portions of the SAM database at a time. The SAM database is returned as a list of deltas like those returned from I_NetDatabaseDeltas. The following deltas are returned for each domain: * One AddOrChangeDomain delta, followed by * One AddOrChangeGroup delta for each group, followed by, * One AddOrChangeUser delta for each user, followed by * One ChangeGroupMembership delta for each group followed by, * One AddOrChangeAlias delta for each alias, followed by, * One ChangeAliasMembership delta for each alias. Arguments: ServerSession -- pointer to connection context. DatabaseID -- Identifies the databse for which the deltas are requested. For SAM database the ID is 0, for Builtin Domain the ID is 1. Other databases may be defined later. RestartState -- Specifies whether this is a restart of the full sync and how to interpret SyncContext. This value should be NormalState unless this is the restart of a full sync. However, if the caller is continuing a full sync after a reboot, the following values are used: GroupState - SyncContext is the global group rid to continue with. UserState - SyncContext is the user rid to continue with GroupMemberState - SyncContext is the global group rid to continue with AliasState - SyncContext should be zero to restart at first alias AliasMemberState - SyncContext should be zero to restart at first alias One cannot continue the LSA database in this way. SyncContext -- Specifies context needed to continue the operation. The caller should treat this as an opaque value. The value should be zero before the first call. DeltaArray -- Pointer to a buffer where the information is placed. The information returned is an array of NETLOGON_DELTA_ENUM structures. PreferredMaximumLength - Preferred maximum length of returned data (in 8-bit bytes). This is not a hard upper limit, but serves as a guide to the server. Due to data conversion between systems with different natural data sizes, the actual amount of data returned may be greater than this value. SessionInfo - Information shared between PDC and BDC. Return Value: STATUS_SUCCESS -- The function completed successfully. STATUS_MORE_ENTRIES -- The replicant should call again to get more data. --*/ { NTSTATUS Status; PSAM_SYNC_CONTEXT SamDBContext; PDB_INFO DBInfo; DWORD BufferConsumed = 0; DWORD BufferSize; DBInfo = &NlGlobalDBInfoArray[DatabaseID]; // // Allocate memory for delta buffer. // DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate( MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ); if( DeltaArray->Deltas == NULL ) { NlPrint((NL_CRITICAL, "NlSyncSamDatabase: Can't allocate %d bytes\n", MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) )); return( STATUS_NO_MEMORY ); } // // wipe off the buffer so that cleanup will not be in fault. // RtlZeroMemory( DeltaArray->Deltas, MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ); // // If this is the first call or an explicit restart call, // allocate and initialize the sync context. // if ( *SyncContext == 0 || RestartState != NormalState ) { // // If there already is a sync context, // delete it. // if ( ServerSession->SsSync != NULL ) { CLEAN_SYNC_CONTEXT( ServerSession->SsSync ); } else { ServerSession->SsSync = NetpMemoryAllocate( sizeof(SYNC_CONTEXT) ); if ( ServerSession->SsSync == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } } // // Initialize all the fields in the newly allocated resume handle // to indicate that SAM has never yet been called. // INIT_SYNC_CONTEXT( ServerSession->SsSync, SamDBContextType ); SamDBContext = &(ServerSession->SsSync->DBContext.Sam); SamDBContext->SyncSerial = 1; // // Compute the continuation state based on the input parameters // switch ( RestartState ) { case NormalState: // // Put the description of the Domain at the front of the buffer for the // first call. // Status = NlPackSamDomain( &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize ); (DeltaArray->CountReturned)++; BufferConsumed += BufferSize; if ( !NT_SUCCESS(Status) ) { goto Cleanup; } SamDBContext->SyncState = GroupState; SamDBContext->SamEnumHandle = 0; break; case AliasState: case AliasMemberState: if ( *SyncContext != 0 ) { NlPrint(( NL_CRITICAL, "NlSyncSamDatabase: Cannot restart alias enumeration.\n" )); Status = STATUS_INVALID_PARAMETER; goto Cleanup; } /* Drop Through */ case GroupState: case UserState: case GroupMemberState: SamDBContext->SyncState = RestartState; SamDBContext->SamEnumHandle = *SyncContext; break; default: NlPrint(( NL_CRITICAL, "NlSyncSamDatabase: Invalid RestartState passed %ld.\n", RestartState )); Status = STATUS_INVALID_PARAMETER; goto Cleanup; } } else { // NlAssert( ServerSession->SsSync != NULL); if( ServerSession->SsSync == NULL) { Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } NlAssert( ServerSession->SsSync->DBContextType == SamDBContextType); if( ServerSession->SsSync->DBContextType != SamDBContextType ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } SamDBContext = &(ServerSession->SsSync->DBContext.Sam); NlAssert( SamDBContext->SyncSerial == *SyncContext ); if( SamDBContext->SyncSerial != *SyncContext ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } SamDBContext->SyncSerial++; } // // Loop for each entry placed in the output buffer // // Each iteration of the loop below puts one more entry into the array // returned to the caller. The algorithm is split into 2 parts. The // first part checks to see if we need to retrieve more information from // SAM and gets the description of several users or group from SAM in a // single call. The second part puts a single entry into the buffer // returned to the caller. // while ( SamDBContext->SyncState != SamDoneState ) { // // If we've filled out pre-allocated array, // return now. // if ( DeltaArray->CountReturned + MAX_DELTAS_PER_CHANGELOG > MAX_DELTA_COUNT ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } // // Get more information from SAM // // Handle when we've not yet called SAM or we've already consumed // all of the information returned on a previous call to SAM. // // This is a 'while' rather than an 'if' to handle the case // where SAM returns zero entries. // while ( SamDBContext->Index >= SamDBContext->Count ) { // // Free any previous buffer returned from SAM. // if ( ServerSession->SsSync != NULL ) { CLEAN_SYNC_CONTEXT( ServerSession->SsSync ); } // // If we've already gotten everything from SAM, // we've finished all of the groups, // // If we've just done the groups, // go on to do the users. // // If we've just done the users, // go on to do the group memberships. // // If we've just done the group memberships, // go on to do the alias. // // If we've just done the alias, // go on to do the alias membership. // // If we've just done the alias memberships, // we're all done. // if ( SamDBContext->SamAllDone ) { SamDBContext->SamEnumHandle = 0; SamDBContext->Index = 0; SamDBContext->Count = 0; SamDBContext->SamAllDone = FALSE; if (SamDBContext->SyncState == GroupState ) { NlPrint((NL_SYNC, "NlSyncSamDatabase: packing user records.\n")); SamDBContext->SyncState = UserState; } else if (SamDBContext->SyncState == UserState ) { NlPrint((NL_SYNC, "NlSyncSamDatabase: " "packing groupmember records.\n")); SamDBContext->SyncState = GroupMemberState; } else if (SamDBContext->SyncState == GroupMemberState ){ NlPrint((NL_SYNC, "NlSyncSamDatabase: packing alias records.\n")); SamDBContext->SyncState = AliasState; } else if (SamDBContext->SyncState == AliasState ){ NlPrint((NL_SYNC, "NlSyncSamDatabase: " " packing aliasmember records.\n")); SamDBContext->SyncState = AliasMemberState ; } else if (SamDBContext->SyncState == AliasMemberState ){ NlPrint((NL_SYNC, "NlSyncSamDatabase: packing done.\n")); SamDBContext->SyncState = SamDoneState; Status = STATUS_SUCCESS; } break; } // // Do the actual enumeration // if (SamDBContext->SyncState == GroupState || SamDBContext->SyncState == GroupMemberState ) { Status = SamIEnumerateAccountRids( DBInfo->DBHandle, SAM_GLOBAL_GROUP_ACCOUNT, SamDBContext->SamEnumHandle, // Return RIDs greater than this SAM_SYNC_PREF_MAX, &SamDBContext->Count, &SamDBContext->RidArray ); if ( !NT_SUCCESS( Status ) ) { SamDBContext->RidArray = NULL; goto Cleanup; } if ( SamDBContext->Count != 0 ) { SamDBContext->SamEnumHandle = SamDBContext->RidArray[SamDBContext->Count-1]; } } else if (SamDBContext->SyncState == UserState ) { Status = SamIEnumerateAccountRids( DBInfo->DBHandle, SAM_USER_ACCOUNT, SamDBContext->SamEnumHandle, // Return RIDs greater than this SAM_SYNC_PREF_MAX, &SamDBContext->Count, &SamDBContext->RidArray ); if ( !NT_SUCCESS( Status ) ) { SamDBContext->RidArray = NULL; goto Cleanup; } if ( SamDBContext->Count != 0 ) { SamDBContext->SamEnumHandle = SamDBContext->RidArray[SamDBContext->Count-1]; } } else if (SamDBContext->SyncState == AliasState || SamDBContext->SyncState == AliasMemberState ) { Status = SamrEnumerateAliasesInDomain( DBInfo->DBHandle, &SamDBContext->SamEnumHandle, &SamDBContext->SamEnum, SAM_SYNC_PREF_MAX, &SamDBContext->Count ); if ( !NT_SUCCESS( Status ) ) { SamDBContext->SamEnum = NULL; goto Cleanup; } NlAssert( SamDBContext->Count == SamDBContext->SamEnum->EntriesRead ); } // // If SAM says there is more information, // just ensure he returned something to us on this call. // if ( Status == STATUS_MORE_ENTRIES ) { // NlAssert( SamDBContext->Count != 0 ); // // If SAM says he's returned all of the information, // remember not to ask SAM for more. // } else { SamDBContext->SamAllDone = TRUE; } SamDBContext->Index = 0; } // // Place this entry into the return buffer. // if ( SamDBContext->Count > 0 ) { if (SamDBContext->SyncState == GroupState ) { Status = NlPackSamGroup( SamDBContext->RidArray[SamDBContext->Index], &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize ); } else if (SamDBContext->SyncState == UserState ) { Status = NlPackSamUser( SamDBContext->RidArray[SamDBContext->Index], &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize, SessionInfo ); } else if (SamDBContext->SyncState == GroupMemberState ) { Status = NlPackSamGroupMember( SamDBContext->RidArray[SamDBContext->Index], &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize ); } else if (SamDBContext->SyncState == AliasState ) { Status = NlPackSamAlias( SamDBContext->SamEnum-> Buffer[SamDBContext->Index].RelativeId, &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize ); } else if (SamDBContext->SyncState == AliasMemberState ) { Status = NlPackSamAliasMember( SamDBContext->SamEnum-> Buffer[SamDBContext->Index].RelativeId, &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize ); } // // If there was a real error or this group didn't fit, // return to the caller. // if ( Status != STATUS_SUCCESS ) { goto Cleanup; } SamDBContext->Index ++; (DeltaArray->CountReturned)++; BufferConsumed += (sizeof(NETLOGON_DELTA_ENUM) + BufferSize); if( BufferConsumed >= PreferredMaximumLength) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } // // If we're debugging replication, return only one change to the caller. // #if NETLOGONDBG if ( NlGlobalParameters.DbFlag & NL_ONECHANGE_REPL ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } #endif // NETLOGONDBG // // if the service is going down, stop packing records and // return to the caller. // // Don't alarm the caller with the status code. He'll find out // on the next call that we're no longer here. // if( NlGlobalTerminate ) { NlPrint((NL_CRITICAL, "NetrDatabaseSync is asked to return " "when the service is going down.\n")); Status = STATUS_MORE_ENTRIES; goto Cleanup; } } } Cleanup: // // Set the return parameters to the proper values. // if ( NT_SUCCESS( Status ) ) { *SyncContext = SamDBContext->SyncSerial; } else { if ( DeltaArray->Deltas != NULL ) { NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned ); DeltaArray->Deltas = NULL; } DeltaArray->CountReturned = 0; *SyncContext = 0; NlPrint((NL_CRITICAL, "NlSyncSamDatabase: returning unsuccessful (%lx).\n", Status)); } return Status; } NTSTATUS NlSyncLsaDatabase( IN PSERVER_SESSION ServerSession, IN OUT PULONG SyncContext, IN OUT PNETLOGON_DELTA_ENUM_ARRAY DeltaArray, IN DWORD PreferredMaximumLength, IN PSESSION_INFO SessionInfo ) /*++ Routine Description: This function is a real worker for the NetrDatabaseSync function and retrieves the LSA database in the delta buffer. This function uses the find-first find-next model to return portions of the SAM database at a time. The SAM database is returned as a list of deltas like those returned from I_NetDatabaseDeltas. The following deltas are returned for each domain: * One AddOrChangeLsaPolicy delta, followed by, * One AddOrChangeLsaAccounts delta for each lsa account, followed by, * One AddOrChangeLsaTDomain delta for each trusted domain, followed by, * One AddOrChangeLsaSecret delta for each lsa secret. Arguments: ServerSession -- pointer to connection context. SyncContext -- Specifies context needed to continue the operation. The caller should treat this as an opaque value. The value should be zero before the first call. DeltaArray -- Pointer to a buffer where the information is placed. The information returned is an array of NETLOGON_DELTA_ENUM structures. PreferredMaximumLength - Preferred maximum length of returned data (in 8-bit bytes). This is not a hard upper limit, but serves as a guide to the server. Due to data conversion between systems with different natural data sizes, the actual amount of data returned may be greater than this value. SessionInfo - Information shared between PDC and BDC. Return Value: STATUS_SUCCESS -- The function completed successfully. STATUS_MORE_ENTRIES -- The replicant should call again to get more data. --*/ { NTSTATUS Status; PLSA_SYNC_CONTEXT LsaDBContext; PDB_INFO DBInfo; DWORD BufferConsumed = 0; DWORD BufferSize; BOOL IgnoreDeltaObject = FALSE; DBInfo = &NlGlobalDBInfoArray[LSA_DB]; // // Allocate memory for delta buffer. // DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate( MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ); if( DeltaArray->Deltas == NULL ) { NlPrint((NL_CRITICAL, "NlSyncLsaDatabase: Can't allocate %d bytes\n", MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) )); return( STATUS_NO_MEMORY ); } // // wipe off the buffer so that cleanup will not be in fault. // RtlZeroMemory( DeltaArray->Deltas, MAX_DELTA_COUNT * sizeof(NETLOGON_DELTA_ENUM) ); // // If this is the first call, allocate and initialize the sync context. // if ( *SyncContext == 0 ) { // // If there already is a sync context, // delete it. // if ( ServerSession->SsSync != NULL ) { CLEAN_SYNC_CONTEXT( ServerSession->SsSync ); } else { ServerSession->SsSync = NetpMemoryAllocate( sizeof(SYNC_CONTEXT) ); if ( ServerSession->SsSync == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } } // // Initialize all the fields in the newly allocated resume handle // to indicate that SAM has never yet been called. // INIT_SYNC_CONTEXT( ServerSession->SsSync, LsaDBContextType ); LsaDBContext = &(ServerSession->SsSync->DBContext.Lsa); LsaDBContext->SyncState = AccountState; LsaDBContext->SyncSerial = 1; LsaDBContext->LsaEnumBufferType = EmptyEnumBuffer; NlPrint((NL_SYNC, "NlSyncLsaDatabase: " "Starting full sync, packing lsa account records\n")); // // Put the description of the Policy at the front of the buffer for the // first call. // Status = NlPackLsaPolicy( &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize ); (DeltaArray->CountReturned)++; BufferConsumed += BufferSize; if ( !NT_SUCCESS(Status) ) { goto Cleanup; } } else { if( ServerSession->SsSync == NULL ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } NlAssert( ServerSession->SsSync->DBContextType == LsaDBContextType); if( ServerSession->SsSync->DBContextType != LsaDBContextType) { Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } LsaDBContext = &(ServerSession->SsSync->DBContext.Lsa); NlAssert( LsaDBContext->SyncSerial == *SyncContext ); if( LsaDBContext->SyncSerial != *SyncContext ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } LsaDBContext->SyncSerial++; } // // Loop for each entry placed in the output buffer // // Each iteration of the loop below puts one more entry into the array // returned to the caller. The algorithm is split into 2 parts. // The first part checks to see if we need to retrieve more information // from LSA and gets the description of several accounts, TDomain or // Secret from LSA in a single call. The second part puts a single // entry into the buffer returned to the caller. // while ( LsaDBContext->SyncState != LsaDoneState ) { // // If we've filled out pre-allocated array, // return now. // if ( DeltaArray->CountReturned + MAX_DELTAS_PER_CHANGELOG > MAX_DELTA_COUNT ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } // // Get more information from LSA // // Handle when we've not yet called LSA or we've already consumed // all of the information returned on a previous call to SAM. // // This is a 'while' rather than an 'if' to handle the case // where LSA returns zero entries. // while ( LsaDBContext->Index >= LsaDBContext->Count ) { // // Free any previous buffer returned from SAM. // if ( ServerSession->SsSync != NULL ) { CLEAN_SYNC_CONTEXT( ServerSession->SsSync ); } // // If we've already gotten everything from LSA, // we've finished all of the accounts, // // If we've just done the accounts, // go on to do the TDomains. // // If we've just done the TDomains, // go on to do the Secrets // // If we've just done the Secret, // we're all done. // if ( LsaDBContext->LsaAllDone ) { LsaDBContext->LsaEnumHandle = 0; LsaDBContext->Index = 0; LsaDBContext->Count = 0; LsaDBContext->LsaAllDone = FALSE; if (LsaDBContext->SyncState == AccountState ) { NlPrint((NL_SYNC, "NlSyncLsaDatabase: " " packing TDomain records.\n")); LsaDBContext->SyncState = TDomainState; } else if (LsaDBContext->SyncState == TDomainState ) { NlPrint((NL_SYNC, "NlSyncLsaDatabase: packing secret records.\n")); LsaDBContext->SyncState = SecretState; } else if (LsaDBContext->SyncState == SecretState ) { NlPrint((NL_SYNC, "NlSyncLsaDatabase: packing done.\n")); LsaDBContext->SyncState = LsaDoneState; LsaDBContext->LsaEnumBufferType = EmptyEnumBuffer; Status = STATUS_SUCCESS; } break; } if (LsaDBContext->SyncState == AccountState ) { LsaDBContext->LsaEnumBufferType = AccountEnumBuffer; Status = LsarEnumerateAccounts( DBInfo->DBHandle, &LsaDBContext->LsaEnumHandle, &LsaDBContext->LsaEnum.Account, SAM_SYNC_PREF_MAX); if (Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) { LsaDBContext->Count = LsaDBContext->LsaEnum.Account.EntriesRead; } } else if (LsaDBContext->SyncState == TDomainState ) { LsaDBContext->LsaEnumBufferType = TDomainEnumBuffer; Status = LsarEnumerateTrustedDomains( DBInfo->DBHandle, &LsaDBContext->LsaEnumHandle, &LsaDBContext->LsaEnum.TDomain, SAM_SYNC_PREF_MAX); if (Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) { LsaDBContext->Count = LsaDBContext->LsaEnum.TDomain.EntriesRead; } } else if (LsaDBContext->SyncState == SecretState ) { LsaDBContext->LsaEnumBufferType = SecretEnumBuffer; Status = LsaIEnumerateSecrets( DBInfo->DBHandle, &LsaDBContext->LsaEnumHandle, &LsaDBContext->LsaEnum.Secret, SAM_SYNC_PREF_MAX, &LsaDBContext->Count ); } // // If LSA says there is more information, // just ensure he returned something to us on this call. // if ( Status == STATUS_SUCCESS || Status == STATUS_MORE_ENTRIES ) { NlAssert( LsaDBContext->Count != 0 ); // // If LSA says he's returned all of the information, // remember not to ask it for more. // } else if ( Status == STATUS_NO_MORE_ENTRIES ) { LsaDBContext->LsaAllDone = TRUE; LsaDBContext->Count = 0; // // Any other error is fatal // } else { LsaDBContext->LsaEnumBufferType = EmptyEnumBuffer; LsaDBContext->Count = 0; goto Cleanup; } LsaDBContext->Index = 0; } // // Place this entry into the return buffer. // if ( LsaDBContext->Count > 0 ) { if (LsaDBContext->SyncState == AccountState ) { Status = NlPackLsaAccount( LsaDBContext->LsaEnum.Account. Information[LsaDBContext->Index].Sid, &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize, SessionInfo ); if ( Status == STATUS_OBJECT_NAME_NOT_FOUND ) { Status = STATUS_SUCCESS; IgnoreDeltaObject = TRUE; BufferSize = 0; } } else if (LsaDBContext->SyncState == TDomainState ) { Status = NlPackLsaTDomain( LsaDBContext->LsaEnum.TDomain. Information[LsaDBContext->Index].Sid, &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize ); } else if (LsaDBContext->SyncState == SecretState ) { PUNICODE_STRING SecretName; SecretName = &((PUNICODE_STRING)LsaDBContext->LsaEnum.Secret) [LsaDBContext->Index]; // // ignore local secret objects. // if( (SecretName->Length / sizeof(WCHAR) > LSA_GLOBAL_SECRET_PREFIX_LENGTH ) && (_wcsnicmp( SecretName->Buffer, LSA_GLOBAL_SECRET_PREFIX, LSA_GLOBAL_SECRET_PREFIX_LENGTH ) == 0)) { Status = NlPackLsaSecret( SecretName, &((DeltaArray->Deltas)[DeltaArray->CountReturned]), DBInfo, &BufferSize, SessionInfo ); } else { Status = STATUS_SUCCESS; IgnoreDeltaObject = TRUE; BufferSize = 0; } } // // If there was a real error or this group didn't fit, // return to the caller. // if ( Status != STATUS_SUCCESS ) { goto Cleanup; } LsaDBContext->Index ++; // // if this object is ignored, don't modify return values. // if ( !IgnoreDeltaObject ) { (DeltaArray->CountReturned)++; BufferConsumed += (sizeof(NETLOGON_DELTA_ENUM) + BufferSize); if( BufferConsumed >= PreferredMaximumLength) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } // // If we're debugging replication, return only one change to the caller. // #if NETLOGONDBG if ( NlGlobalParameters.DbFlag & NL_ONECHANGE_REPL ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } #endif // NETLOGONDBG } else { IgnoreDeltaObject = FALSE; } } } Cleanup: // // Set the return parameters to the proper values. // if ( NT_SUCCESS( Status ) ) { *SyncContext = LsaDBContext->SyncSerial; } else { if ( DeltaArray->Deltas != NULL ) { NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned ); DeltaArray->Deltas = NULL; } DeltaArray->CountReturned = 0; *SyncContext = 0; } if (!NT_SUCCESS(Status)) { NlPrint((NL_CRITICAL, "NlSyncLsaDatabase: returning unsuccessful (%lx).\n", Status)); } return Status; } NTSTATUS NetrDatabaseSync ( IN LPWSTR PrimaryName, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN DWORD DatabaseID, IN OUT PULONG SyncContext, OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet, IN DWORD PreferredMaximumLength ) /*++ Routine Description: NT 3.1 version of NetrDatabaseSync2. Don't pass the RestartState parameter. Sync Context is all that is needed to identify the state. Arguments: Same as NetrDatabaseSync2 (with the exception mentioned above). Return Value: Save as NetrDatabaseSync2. --*/ { return NetrDatabaseSync2( PrimaryName, ComputerName, Authenticator, ReturnAuthenticator, DatabaseID, NormalState, SyncContext, DeltaArrayRet, PreferredMaximumLength ); } NTSTATUS NetrDatabaseSync2 ( IN LPWSTR PrimaryName, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN DWORD DatabaseID, IN SYNC_STATE RestartState, IN OUT PULONG SyncContext, OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet, IN DWORD PreferredMaximumLength ) /*++ Routine Description: This function is used by an NT BDC to request the entire SAM/LSA database from a PDC in NTLANMAN-style format. This function can only be called by a server which has previously authenticated with the PDC by calling I_NetServerAuthenticate. This function uses RPC to contact the Netlogon service on the PDC. Arguments: PrimaryName -- Name of the PDC to retrieve the deltas from. ComputerName -- Name of the BDC or member server making the call. Authenticator -- supplied by the server. ReturnAuthenticator -- Receives an authenticator returned by the PDC. DatabaseID -- Identifies the databse for which the deltas are requested. For SAM database the ID is 0, for Builtin Domain the ID is 1. Other databases may be defined later. RestartState -- Specifies whether this is a restart of the full sync and how to interpret SyncContext. This value should be NormalState unless this is the restart of a full sync. However, if the caller is continuing a full sync after a reboot, the following values are used: GroupState - SyncContext is the global group rid to continue with. UserState - SyncContext is the user rid to continue with GroupMemberState - SyncContext is the global group rid to continue with AliasState - SyncContext should be zero to restart at first alias AliasMemberState - SyncContext should be zero to restart at first alias One cannot continue the LSA database in this way. SyncContext -- Specifies context needed to continue the operation. The caller should treat this as an opaque value. The value should be zero before the first call. DeltaArray -- Receives a pointer to a buffer where the information is placed. The information returned is an array of NETLOGON_DELTA_ENUM structures. PreferredMaximumLength - Preferred maximum length of returned data (in 8-bit bytes). This is not a hard upper limit, but serves as a guide to the server. Due to data conversion between systems with different natural data sizes, the actual amount of data returned may be greater than this value. Return Value: STATUS_SUCCESS -- The function completed successfully. STATUS_MORE_ENTRIES -- The replicant should call again to get more data. STATUS_ACCESS_DENIED -- The replicant should re-authenticate with the PDC. --*/ { NTSTATUS Status; PDOMAIN_INFO DomainInfo = NULL; PSERVER_SESSION ServerSession = NULL; PNETLOGON_DELTA_ENUM_ARRAY DeltaArray; SESSION_INFO SessionInfo; PDB_INFO DBInfo; DEFSSIAPITIMER; INITSSIAPITIMER; STARTSSIAPITIMER; // // This API is not supported on workstations. // if ( NlGlobalMemberWorkstation || !NlGlobalPdcDoReplication ) { NlPrint((NL_CRITICAL, "NetrDatabaseSync2: called from %ws. This machine doesn't support replication.\n", ComputerName )); return STATUS_NOT_SUPPORTED; } // // Gross hack because of RPC implementation. // // Rpc executes API calls in an I/O completion port thread. If this thread // goes CPU bound, then no other RPC will be allowed to start. Even worse, // there is only one outstanding listen, so the 'second' coming RPC call // gets RPC_S_SERVER_TOO_BUSY. // // By sleeping here (even for a short period) the I/O completion port releases // another thread since it thinks this thread went I/O bound. // // We've seen this thread go CPU bound doing a full sync of a database with // 1000's of LSA account objects. // RpcServerYield(); // // If the DS is recovering from a backup, // avoid changing the DS. // if ( NlGlobalDsPaused ) { NlPrint((NL_CRITICAL, "NetrDatabaseSync2: DsIsPaused.\n")); // Don't return a new status code since NT 4 DC would do a full sync return STATUS_ACCESS_DENIED; } if ( DatabaseID >= NUM_DBS ) { return STATUS_INVALID_LEVEL; } DBInfo = &NlGlobalDBInfoArray[DatabaseID]; // // Initialization // *DeltaArrayRet = DeltaArray = (PNETLOGON_DELTA_ENUM_ARRAY) MIDL_user_allocate( sizeof(NETLOGON_DELTA_ENUM_ARRAY) ); if( DeltaArray == NULL ) { return(STATUS_NO_MEMORY); } DeltaArray->Deltas = NULL; DeltaArray->CountReturned = 0; // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( PrimaryName ); NlPrintDom((NL_SYNC, DomainInfo, "NetrDatabaseSync: " FORMAT_LPWSTR " full sync called by " FORMAT_LPWSTR " State: %ld Context: 0x%lx.\n", DBInfo->DBName, ComputerName, RestartState, *SyncContext )); if ( DomainInfo == NULL ) { Status = STATUS_INVALID_COMPUTER_NAME; goto Cleanup; } if ( !IsPrimaryDomain( DomainInfo )) { Status = STATUS_NOT_SUPPORTED; goto Cleanup; } // // Retrieve the requestor's entry to get sessionkey // LOCK_SERVER_SESSION_TABLE( DomainInfo ); ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; // Don't log this event since it happens in nature after a reboot // or after we scavenge the server session. NlPrint((NL_CRITICAL, "NetrDatabaseSync: No server session.\n")); goto CleanupNoEventlog; } // // Allow this call only on ServerSecureChannel. // if( ServerSession->SsSecureChannelType != ServerSecureChannel ) { // // If the only preblem is that this BDC hasn't authenticated, // silently ask it to authenticate. // if ( ServerSession->SsSecureChannelType == NullSecureChannel ) { NlPrint((NL_CRITICAL, "NetrDatabaseSync: No authenticated server session.\n")); ServerSession = NULL; UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); // Don't log this event since it happens in nature after a reboot // or after we scavenge the server session. Status = STATUS_ACCESS_DENIED; goto CleanupNoEventlog; } else { NlPrint((NL_CRITICAL, "NetrDatabaseSync: SecureChannel type isn't BDC. %ld\n", ServerSession->SsSecureChannelType )); ServerSession = NULL; UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; goto Cleanup; } } // // Verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); NlPrint((NL_CRITICAL, "NetrDatabaseSync: authentication failed.\n" )); ServerSession = NULL; goto Cleanup; } // // Prevent entry from being deleted, but drop the global lock. // // Beware of server with two concurrent calls outstanding // (must have rebooted.) // if (ServerSession->SsFlags & SS_LOCKED ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); NlPrint((NL_CRITICAL, "NetrDatabaseSync: Concurrent call detected.\n" )); Status = STATUS_ACCESS_DENIED; ServerSession = NULL; goto Cleanup; } ServerSession->SsFlags |= SS_LOCKED; SessionInfo.SessionKey = ServerSession->SsSessionKey; SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags; UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); if( DatabaseID == LSA_DB ) { NlAssert( RestartState == NormalState ); Status = NlSyncLsaDatabase( ServerSession, SyncContext, DeltaArray, PreferredMaximumLength, &SessionInfo ); } else { Status = NlSyncSamDatabase( ServerSession, DatabaseID, RestartState, SyncContext, DeltaArray, PreferredMaximumLength, &SessionInfo ); } Cleanup: // // write event log // if ( !NT_SUCCESS( Status ) ) { LPWSTR MsgStrings[2]; MsgStrings[0] = ComputerName; MsgStrings[1] = (LPWSTR) LongToPtr( Status ); NlpWriteEventlog( NELOG_NetlogonFullSyncCallFailed, EVENTLOG_WARNING_TYPE, (LPBYTE)&Status, sizeof(Status), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NTSTATUS | NETP_ALLOW_DUPLICATE_EVENTS ); } else { LPWSTR MsgStrings[2]; WCHAR CountBuffer[20]; // random size MsgStrings[0] = ComputerName; ultow( DeltaArray->CountReturned, CountBuffer, 10); MsgStrings[1] = CountBuffer; NlpWriteEventlog( NELOG_NetlogonFullSyncCallSuccess, EVENTLOG_INFORMATION_TYPE, NULL, 0, MsgStrings, 2 | NETP_ALLOW_DUPLICATE_EVENTS ); } // // Unlock the server session entry if we've locked it. // CleanupNoEventlog: if ( ServerSession != NULL ) { // // If we're done, free up the context structure, // if ( Status != STATUS_MORE_ENTRIES && ServerSession->SsSync != NULL ) { CLEAN_SYNC_CONTEXT( ServerSession->SsSync ); NetpMemoryFree( ServerSession->SsSync ); ServerSession->SsSync = NULL; } // // If we are successfully returning these deltas to the BDC, // update our tables to reflect the changes. // if ( Status == STATUS_SUCCESS ) { NlPrimaryAnnouncementFinish( ServerSession, DatabaseID, NULL ); } NlUnlockServerSession( ServerSession ); } // // If the BDC called us just as SAM was shutting down, // map the status to prevent the BDC from full syncing. // if ( Status == STATUS_INVALID_SERVER_STATE ) { Status = STATUS_ACCESS_DENIED; } NlPrint((NL_SYNC, "NetrDatabaseSync: " FORMAT_LPWSTR " returning (0x%lx) to " FORMAT_LPWSTR " Context: 0x%lx.\n", DBInfo->DBName, Status, ComputerName, *SyncContext )); STOPSSIAPITIMER; NlPrint((NL_REPL_TIME,"NetrDatabaseSync Time:\n")); PRINTSSIAPITIMER; if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } return Status; } NTSTATUS NetrDatabaseRedo( IN LPWSTR PrimaryName, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN LPBYTE OrigChangeLogEntry, IN DWORD ChangeLogEntrySize, OUT PNETLOGON_DELTA_ENUM_ARRAY *DeltaArrayRet ) /*++ Routine Description: This function is used by a SAM BDC to request infomation about a single account. This function can only be called by a server which has previously authenticated with the PDC by calling I_NetServerAuthenticate. This function uses RPC to contact the Netlogon service on the PDC. Arguments: PrimaryName -- Name of the PDC to retrieve the delta from. ComputerName -- Name of the BDC making the call. Authenticator -- supplied by the server. ReturnAuthenticator -- Receives an authenticator returned by the PDC. ChangeLogEntry -- A description of the account to be queried. ChangeLogEntrySize -- Size (in bytes) of the ChangeLogEntry. DeltaArrayRet -- Receives a pointer to a buffer where the information is placed. The information returned is an array of NETLOGON_DELTA_ENUM structures. Return Value: STATUS_SUCCESS -- The function completed successfully. STATUS_ACCESS_DENIED -- The replicant should re-authenticate with the PDC. --*/ { PCHANGELOG_ENTRY ChangeLogEntry; NTSTATUS Status; PDOMAIN_INFO DomainInfo = NULL; PSERVER_SESSION ServerSession = NULL; LPWSTR MsgStrings[2]; DWORD BufferSize; PNETLOGON_DELTA_ENUM_ARRAY DeltaArray = NULL; SESSION_INFO SessionInfo; DEFSSIAPITIMER; INITSSIAPITIMER; STARTSSIAPITIMER; // // This API is not supported on workstations. // if ( NlGlobalMemberWorkstation || !NlGlobalPdcDoReplication ) { NlPrint((NL_CRITICAL, "NetrDatabaseRedo: called from %ws. This machine doesn't support replication.\n", ComputerName )); return STATUS_NOT_SUPPORTED; } // // If the DS is recovering from a backup, // avoid changing the DS. // if ( NlGlobalDsPaused ) { NlPrint((NL_CRITICAL, "NetrDatabaseRedo: DsIsPaused.\n")); // Don't return a new status code since NT 4 DC would do a full sync return STATUS_ACCESS_DENIED; } // // Initialization // ChangeLogEntry = (PCHANGELOG_ENTRY) OrigChangeLogEntry; if ( !NlValidateChangeLogEntry( ChangeLogEntry, ChangeLogEntrySize ) || ChangeLogEntry->DBIndex >= NUM_DBS ) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; } // // Find the domain this API was made to. // DomainInfo = NlFindDomainByServerName( PrimaryName ); NlPrintDom((NL_SYNC, DomainInfo, "NetrDatabaseRedo: " FORMAT_LPWSTR " redo sync called by " FORMAT_LPWSTR " with this change log entry:\n", NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName, ComputerName )); if ( DomainInfo == NULL ) { Status = STATUS_INVALID_COMPUTER_NAME; goto Cleanup; } if ( !IsPrimaryDomain( DomainInfo )) { Status = STATUS_NOT_SUPPORTED; goto Cleanup; } #if NETLOGONDBG PrintChangeLogEntry( ChangeLogEntry ); #endif // NETLOGONDBG // // The change log entry really represents an object and not an operation. // Therefore, convert the delta type from whatever was passed to an // "AddOrChange" operation. Then NlPackSingleDelta will return everything // we know about the object. // ChangeLogEntry->DeltaType = (UCHAR)NlGlobalAddDeltaType[ChangeLogEntry->DeltaType]; *DeltaArrayRet = DeltaArray = (PNETLOGON_DELTA_ENUM_ARRAY) MIDL_user_allocate( sizeof(NETLOGON_DELTA_ENUM_ARRAY) ); if( DeltaArray == NULL ) { return STATUS_NO_MEMORY; } DeltaArray->CountReturned = 0; DeltaArray->Deltas = NULL; SessionInfo.NegotiatedFlags = 0; // // Retrieve the requestor's entry to get sessionkey // LOCK_SERVER_SESSION_TABLE( DomainInfo ); ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName ); if (ServerSession == NULL) { NlPrint((NL_CRITICAL, "NetrDatabaseRedo: No server session.\n")); UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; // Don't log this event since it happens in nature after a reboot // or after we scavenge the server session. goto CleanupNoEventlog; } // // Allow this call only on ServerSecureChannel. // if( ServerSession->SsSecureChannelType != ServerSecureChannel ) { // // If the only preblem is that this BDC hasn't authenticated, // silently ask it to authenticate. // if ( ServerSession->SsSecureChannelType == NullSecureChannel ) { NlPrint((NL_CRITICAL, "NetrDatabaseRedo: No authenticated server session.\n")); ServerSession = NULL; UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); // Don't log this event since it happens in nature after a reboot // or after we scavenge the server session. Status = STATUS_ACCESS_DENIED; goto CleanupNoEventlog; } else { NlPrint((NL_CRITICAL, "NetrDatabaseRedo: SecureChannel type isn't BDC. %ld\n", ServerSession->SsSecureChannelType )); ServerSession = NULL; UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; goto Cleanup; } } // // Verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); NlPrint((NL_CRITICAL, "NetrDatabaseRedo: authentication failed.\n" )); ServerSession = NULL; goto Cleanup; } // // Prevent entry from being deleted, but drop the global lock. // // Beware of server with two concurrent calls outstanding // (must have rebooted.) // if (ServerSession->SsFlags & SS_LOCKED ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); NlPrint((NL_CRITICAL, "NetrDatabaseRedo: Concurrent call detected.\n" )); Status = STATUS_ACCESS_DENIED; ServerSession = NULL; goto Cleanup; } ServerSession->SsFlags |= SS_LOCKED; SessionInfo.SessionKey = ServerSession->SsSessionKey; SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags; UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); // // Allocate memory for delta buffer. // DeltaArray->Deltas = (PNETLOGON_DELTA_ENUM) MIDL_user_allocate( MAX_DELTAS_PER_CHANGELOG * sizeof(NETLOGON_DELTA_ENUM) ); if( DeltaArray->Deltas == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } // // wipe off the buffer so that cleanup will not be in fault. // RtlZeroMemory( DeltaArray->Deltas, MAX_DELTAS_PER_CHANGELOG * sizeof(NETLOGON_DELTA_ENUM) ); // // Put the data for the changelog entry into the user's buffer. // Status = NlPackSingleDelta( ChangeLogEntry, DeltaArray, &BufferSize, &SessionInfo, FALSE ); // // If the only problem is that the object no longer exists, // return a delta asking the BDC to delete the object. // if ( !NT_SUCCESS(Status) && IsObjectNotFoundStatus( ChangeLogEntry->DeltaType, Status ) ) { NlPrint((NL_SYNC, "NetrDatabaseRedo: " FORMAT_LPWSTR " object no longer exists (0x%lx) " FORMAT_LPWSTR "\n", NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName, Status, ComputerName )); // // Convert the change log entry into an appropriate delete delta type and // try again. // ChangeLogEntry->DeltaType = (UCHAR)NlGlobalDeleteDeltaType[ChangeLogEntry->DeltaType]; Status = NlPackSingleDelta( ChangeLogEntry, DeltaArray, &BufferSize, &SessionInfo, FALSE ); } Cleanup: // // write event log // if ( !NT_SUCCESS( Status ) ) { MsgStrings[0] = ComputerName; MsgStrings[1] = (LPWSTR) LongToPtr( Status ); NlpWriteEventlog( NELOG_NetlogonPartialSyncCallFailed, EVENTLOG_WARNING_TYPE, (LPBYTE)&Status, sizeof(Status), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NTSTATUS | NETP_ALLOW_DUPLICATE_EVENTS ); } else { // // Log the successful replication only if deltas have been returned // to the caller. // if ( DeltaArray->CountReturned != 0 ) { LPWSTR MsgStrings[2]; WCHAR CountBuffer[20]; // random size MsgStrings[0] = ComputerName; ultow( DeltaArray->CountReturned, CountBuffer, 10); MsgStrings[1] = CountBuffer; NlpWriteEventlog( NELOG_NetlogonPartialSyncCallSuccess, EVENTLOG_INFORMATION_TYPE, NULL, 0, MsgStrings, 2 | NETP_ALLOW_DUPLICATE_EVENTS ); } } // // Free up locally allocated resources. // CleanupNoEventlog: // // If we weren't successful, // Don't return any deltas. // if ( !NT_SUCCESS(Status)) { if ( DeltaArray != NULL ) { if ( DeltaArray->Deltas != NULL ) { NlFreeDBDeltaArray( DeltaArray->Deltas, DeltaArray->CountReturned ); DeltaArray->Deltas = NULL; } DeltaArray->CountReturned = 0; } } // // Unlock the server session entry if we've locked it. // if ( ServerSession != NULL ) { NlUnlockServerSession( ServerSession ); } // // If the BDC called us just as SAM was shutting down, // map the status to prevent the BDC from full syncing. // if ( Status == STATUS_INVALID_SERVER_STATE ) { Status = STATUS_ACCESS_DENIED; } NlPrint((NL_SYNC, "NetrDatabaseRedo: " FORMAT_LPWSTR " returning (0x%lx) to " FORMAT_LPWSTR "\n", NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName, Status, ComputerName )); STOPSSIAPITIMER; NlPrint((NL_REPL_TIME,"NetrDatabaseRedo Time:\n")); PRINTSSIAPITIMER; if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } return Status; } NTSTATUS NetrAccountDeltas ( IN LPWSTR PrimaryName, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN PUAS_INFO_0 RecordId, IN DWORD Count, IN DWORD Level, OUT LPBYTE Buffer, IN DWORD BufferSize, OUT PULONG CountReturned, OUT PULONG TotalEntries, OUT PUAS_INFO_0 NextRecordId ) /*++ Routine Description: This function is used by a UAS BDC or UAS member server to request UAS-style account change information. This function can only be called by a server which has previously authenticated with the PDC by calling I_NetServerAuthenticate. This function is only called by the XACT server upon receipt of a I_NetAccountDeltas XACT SMB from a UAS BDC or a UAS member server. As such, many of the parameters are opaque since the XACT server doesn't need to interpret any of that data. This function uses RPC to contact the Netlogon service. The LanMan 3.0 SSI Functional Specification describes the operation of this function. Arguments: PrimaryName -- Must be NULL to indicate this call is a local call being made on behalf of a UAS server by the XACT server. ComputerName -- Name of the BDC or member making the call. Authenticator -- supplied by the server. ReturnAuthenticator -- Receives an authenticator returned by the PDC. RecordId -- Supplies an opaque buffer indicating the last record received from a previous call to this function. Count -- Supplies the number of Delta records requested. Level -- Reserved. Must be zero. Buffer -- Returns opaque data representing the information to be returned. BufferSize -- Size of buffer in bytes. CountReturned -- Returns the number of records returned in buffer. TotalEntries -- Returns the total number of records available. NextRecordId -- Returns an opaque buffer identifying the last record received by this function. Return Value: NT status code. --*/ { NlAssert(!"NetrAccountDeltas called"); UNREFERENCED_PARAMETER( PrimaryName ); UNREFERENCED_PARAMETER( ComputerName ); UNREFERENCED_PARAMETER( Authenticator ); UNREFERENCED_PARAMETER( ReturnAuthenticator ); UNREFERENCED_PARAMETER( RecordId ); UNREFERENCED_PARAMETER( Count ); UNREFERENCED_PARAMETER( Level ); UNREFERENCED_PARAMETER( Buffer ); UNREFERENCED_PARAMETER( BufferSize ); UNREFERENCED_PARAMETER( CountReturned ); UNREFERENCED_PARAMETER( TotalEntries ); UNREFERENCED_PARAMETER( NextRecordId ); return(STATUS_NOT_IMPLEMENTED); } NTSTATUS NetrAccountSync ( IN LPWSTR PrimaryName, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN DWORD Reference, IN DWORD Level, OUT LPBYTE Buffer, IN DWORD BufferSize, OUT PULONG CountReturned, OUT PULONG TotalEntries, OUT PULONG NextReference, OUT PUAS_INFO_0 LastRecordId ) /*++ Routine Description: This function is used by a UAS BDC or UAS member server to request the entire user accounts database. This function can only be called by a server which has previously authenticated with the PDC by calling I_NetServerAuthenticate. This function is only called by the XACT server upon receipt of a I_NetAccountSync XACT SMB from a UAS BDC or a UAS member server. As such, many of the parameters are opaque since the XACT server doesn't need to interpret any of that data. This function uses RPC to contact the Netlogon service. The LanMan 3.0 SSI Functional Specification describes the operation of this function. "reference" and "next_reference" are treated as below. 1. "reference" should hold either 0 or value of "next_reference" from previous call to this API. 2. Send the modals and ALL group records in the first call. The API expects the buffer to be large enough to hold this info (worst case size would be MAXGROUP * (sizeof(struct group_info_1) + MAXCOMMENTSZ) + sizeof(struct user_modals_info_0) which, for now, will be 256 * (26 + 49) + 16 = 19216 bytes Arguments: PrimaryName -- Must be NULL to indicate this call is a local call being made on behalf of a UAS server by the XACT server. ComputerName -- Name of the BDC or member making the call. Authenticator -- supplied by the server. ReturnAuthenticator -- Receives an authenticator returned by the PDC. Reference -- Supplies find-first find-next handle returned by the previous call to this function or 0 if it is the first call. Level -- Reserved. Must be zero. Buffer -- Returns opaque data representing the information to be returned. BufferLen -- Length of buffer in bytes. CountReturned -- Returns the number of records returned in buffer. TotalEntries -- Returns the total number of records available. NextReference -- Returns a find-first find-next handle to be provided on the next call. LastRecordId -- Returns an opaque buffer identifying the last record received by this function. Return Value: NT status code. --*/ { NlAssert(!"NetrAccountDeltas called"); UNREFERENCED_PARAMETER( PrimaryName ); UNREFERENCED_PARAMETER( ComputerName ); UNREFERENCED_PARAMETER( Authenticator ); UNREFERENCED_PARAMETER( ReturnAuthenticator ); UNREFERENCED_PARAMETER( Reference ); UNREFERENCED_PARAMETER( Level ); UNREFERENCED_PARAMETER( Buffer ); UNREFERENCED_PARAMETER( BufferSize ); UNREFERENCED_PARAMETER( CountReturned ); UNREFERENCED_PARAMETER( TotalEntries ); UNREFERENCED_PARAMETER( NextReference ); UNREFERENCED_PARAMETER( LastRecordId ); return(STATUS_NOT_IMPLEMENTED); } NTSTATUS NlGetTrustedSideInfo( IN PCLIENT_SESSION ClientSession, IN LPWSTR AccountName OPTIONAL, IN NETLOGON_SECURE_CHANNEL_TYPE AccountType, OUT PNT_OWF_PASSWORD NewOwfPassword, OUT PNT_OWF_PASSWORD OldOwfPassword, OUT PNL_GENERIC_RPC_DATA *TrustInfo ) /*++ Routine Description: This function is used by a trusting side DC to get the new and old passwords from the trusted side. The caller must be the writer of the client session. Arguments: ClientSession - Identifies a session to the trusted side. The caller must be the writer of this client session. AccountName -- Name of the account to get the password for. If NULL, the account name from the ClientSession is used. AccountType -- The type of account being accessed. Ignored if AccountName is NULL in which case the account type specified in teh ClientSession is used. NewOwfPassword -- Returns the new OWF password of the account. OldOwfPassword -- Returns the old OWF password of the account. TrustInfo -- Returns the trusted domain info Return Value: NT status code. --*/ { NTSTATUS Status; NETLOGON_AUTHENTICATOR OurAuthenticator; NETLOGON_AUTHENTICATOR ReturnAuthenticator; SESSION_INFO SessionInfo; BOOLEAN FirstTry = TRUE; BOOLEAN OldServer = FALSE; ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrNewPassword; ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrOldPassword; NETLOGON_CREDENTIAL CurrentAuthenticationSeed; PNL_GENERIC_RPC_DATA LocalTrustInfo = NULL; // // If the server supports neither the new get_password_and_attributes API // nor the old get_passwords API, there is nothing for us to do here // if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_ATTR_MONITOR) && (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_MONITOR) ) { return STATUS_NOT_SUPPORTED; } // // If the session isn't authenticated, // do so now. // FirstTryFailed: Status = NlEnsureSessionAuthenticated( ClientSession, 0 ); if ( !NT_SUCCESS(Status) ) { goto Cleanup; } SessionInfo.SessionKey = ClientSession->CsSessionKey; SessionInfo.NegotiatedFlags = ClientSession->CsNegotiatedFlags; // // Remember current authentication seed. We may need to reset it // if the trusted side server is the old one that doesn't have // the passwords monitoring API. // CurrentAuthenticationSeed = ClientSession->CsAuthenticationSeed; // // Build the Authenticator for this request to the server // NlBuildAuthenticator( &ClientSession->CsAuthenticationSeed, &ClientSession->CsSessionKey, &OurAuthenticator); // // Get the passwords (and perhaps attributes) from the server // NL_API_START( Status, ClientSession, TRUE ) { NlAssert( ClientSession->CsUncServerName != NULL ); // // If this server may support getting both passwords and attributes, // ask for both // if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_ATTR_MONITOR) == 0 ) { Status = I_NetServerGetTrustInfo( ClientSession->CsUncServerName, (AccountName != NULL) ? AccountName : ClientSession->CsAccountName, (AccountName != NULL) ? AccountType : ClientSession->CsSecureChannelType, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &OurAuthenticator, &ReturnAuthenticator, &SessKeyEncrNewPassword, &SessKeyEncrOldPassword, &LocalTrustInfo ); // // If the server is old that doesn't support this functionality, // remember to never ask it about attributes again and try to // get just the passwords // if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) { ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_NO_PWD_ATTR_MONITOR; } } // // If this server doesn't support getting both passwords and attributes but // may support getting just the passwords, ask for it. // // REVIEW: Ditch this code once there are no more Whistler Beta2 servers out // there which don't support I_NetServerGetTrustInfo. // if ( (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_ATTR_MONITOR) != 0 && (ClientSession->CsDiscoveryFlags & CS_DISCOVERY_NO_PWD_MONITOR) == 0 ) { Status = I_NetServerTrustPasswordsGet( ClientSession->CsUncServerName, (AccountName != NULL) ? AccountName : ClientSession->CsAccountName, (AccountName != NULL) ? AccountType : ClientSession->CsSecureChannelType, ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, &OurAuthenticator, &ReturnAuthenticator, &SessKeyEncrNewPassword, &SessKeyEncrOldPassword ); // // If the server is old that doesn't support this functionality, // remember to never ask it about passwords again // if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) { ClientSession->CsDiscoveryFlags |= CS_DISCOVERY_NO_PWD_MONITOR; } } // // Detect if the trusted side is an old server. If so, avoid dropping the // secure channel in the NL_API_ELSE logic by making it think all was fine. // if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE ) { OldServer = TRUE; Status = STATUS_SUCCESS; } // NOTE: This call may drop the secure channel behind our back } NL_API_ELSE( Status, ClientSession, TRUE ) { } NL_API_END; if ( OldServer ) { goto Cleanup; } // // Now verify primary's authenticator and update our seed // if ( Status == STATUS_ACCESS_DENIED || !NlUpdateSeed( &ClientSession->CsAuthenticationSeed, &ReturnAuthenticator.Credential, &ClientSession->CsSessionKey) ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlGetTrustedSideInfo: denying access after status: 0x%lx\n", Status )); // // Preserve any status indicating a communication error. // if ( NT_SUCCESS(Status) ) { Status = STATUS_ACCESS_DENIED; } NlSetStatusClientSession( ClientSession, Status ); // // Perhaps the netlogon service on the server has just restarted. // Try just once to set up a session to the server again. // if ( FirstTry ) { FirstTry = FALSE; goto FirstTryFailed; } } if ( !NT_SUCCESS(Status) ) { goto Cleanup; } // // Decrypt the password returned from the server. // Status = RtlDecryptNtOwfPwdWithNtOwfPwd( &SessKeyEncrNewPassword, (PNT_OWF_PASSWORD) &SessionInfo.SessionKey, NewOwfPassword ); NlAssert( NT_SUCCESS(Status) ); Status = RtlDecryptNtOwfPwdWithNtOwfPwd( &SessKeyEncrOldPassword, (PNT_OWF_PASSWORD) &SessionInfo.SessionKey, OldOwfPassword ); NlAssert( NT_SUCCESS(Status) ); // // Common exit // Cleanup: // // Remember to not try this call to this server in future. // Reset authentication seed to have the secure channel // working next time we use it. // if ( OldServer ) { ClientSession->CsAuthenticationSeed = CurrentAuthenticationSeed; Status = STATUS_NOT_SUPPORTED; } // // On success, return the trust info // if ( !NT_SUCCESS(Status) ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlGetTrustedSideInfo: %ws: failed %lX\n", AccountName, Status )); if ( LocalTrustInfo != NULL ) { NetApiBufferFree( LocalTrustInfo ); } } else { *TrustInfo = LocalTrustInfo; } return Status; } NTSTATUS NlVerifyTrust( IN PCLIENT_SESSION ClientSession, OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation ) /*++ Routine Description: This function is used by the trusting side to verify the status of the secure channel to the trusted side DC. It first tries to use an API that goes over the secure channel and returns the passwords used for the given trust relationship from the trusted side. The trusting side checks if there is match between the passwords returned from the trusted side and those it has locally. If they match, the API returns success to the caller. If the trusted side lacks this functionality, the trusting side verifies the trust by performing an authentication call over the secure channel for a bogus domain\user. If the secure channel works, this is bound to fail with STATUS_NO_SUCH_USER in which case success is returned to the caller. Arguments: ClientSession - Identifies a session to the trusted side. QueryInformation - Returns a pointer to a NETLOGON_INFO_2 buffer which contains the requested information. The buffer must be freed using NetApiBufferFree. Return Value: NT status code. --*/ { NTSTATUS Status = STATUS_SUCCESS; // Status of operation NTSTATUS SecureChannelStatus = STATUS_SUCCESS; // Status of secure channel NTSTATUS VerificationStatus = STATUS_SUCCESS; // Status of trust verification NT_OWF_PASSWORD NewOwfPassword; NT_OWF_PASSWORD OldOwfPassword; NT_OWF_PASSWORD OurNewOwfPassword; NT_OWF_PASSWORD OurOldOwfPassword; PUNICODE_STRING OurNewPassword = NULL; PUNICODE_STRING OurOldPassword = NULL; ULONG DummyPasswordVersionNumber; LPBYTE ValidationInformation = NULL; LPWSTR ServerName = NULL; ULONG ServerDiscoveryFlags = 0; PNL_GENERIC_RPC_DATA TrustInfo = NULL; BOOL AmWriter = FALSE; BOOL TrustAttribVerified = FALSE; // // Become a Writer of the ClientSession. // if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlVerifyTrust: Can't become writer of client session.\n")); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } AmWriter = TRUE; // // Get the trust passwords from the trusted side // Status = NlGetTrustedSideInfo( ClientSession, NULL, // Use the account specified in the client session NullSecureChannel, // Let the routine get the account type &NewOwfPassword, &OldOwfPassword, &TrustInfo ); // // If this call is not supported on the trusted side DC, // we can only check if the secure chanel is currently healthy. // if ( Status == STATUS_NOT_SUPPORTED ) { NETLOGON_INTERACTIVE_INFO LogonInformation; PNETLOGON_LOGON_IDENTITY_INFO Identity = (PNETLOGON_LOGON_IDENTITY_INFO) &LogonInformation; BOOLEAN Authoritative; WCHAR BogusName[2]; ULONG ExtraFlags = 0; BogusName[0] = (WCHAR) 0xFFFF; BogusName[1] = UNICODE_NULL; // // Reset the status // Status = STATUS_SUCCESS; // // Initialize the structure with bogus names // RtlZeroMemory( &LogonInformation, sizeof(LogonInformation) ); RtlInitUnicodeString( &Identity->LogonDomainName, BogusName ); RtlInitUnicodeString( &Identity->UserName, BogusName ); RtlInitUnicodeString( &Identity->Workstation, BogusName ); // // Release the writer lock as it is used // in the following secure channel call // NlResetWriterClientSession( ClientSession ); AmWriter = FALSE; // // Force a call over the secure channel // Status = NlpUserValidateHigher( ClientSession, FALSE, // not doing indirect trust NetlogonInteractiveInformation, (LPBYTE) &LogonInformation, NetlogonValidationSamInfo, &ValidationInformation, &Authoritative, &ExtraFlags ); // // This is bound to fail. Ignore the failure. // NlAssert( !NT_SUCCESS(Status) ); Status = STATUS_SUCCESS; // // Get the secure channel status after // we made a call over it // SecureChannelStatus = NlCaptureServerClientSession( ClientSession, &ServerName, &ServerDiscoveryFlags ); // // The above is our best we can do to // verify the trust for an old server // VerificationStatus = SecureChannelStatus; // // Otherwise, this is the new server. // Check the secure channel state. If it's successful, // verify the trust status by checking whether local // trust attributes and passwords match with those // received from the trusted side. // } else { // // Get the secure channel status and the server // name. Do this while holding the writer lock // to ensure we return the name of the server // used to verify the trust. // SecureChannelStatus = NlCaptureServerClientSession( ClientSession, &ServerName, &ServerDiscoveryFlags ); // // Release the writer lock. We don't need it anymore. // NlResetWriterClientSession( ClientSession ); AmWriter = FALSE; // // If secure channel is down, there is nothing // to verify // if ( !NT_SUCCESS(SecureChannelStatus) ) { VerificationStatus = SecureChannelStatus; Status = STATUS_SUCCESS; goto Cleanup; } // // OK, secure channel is up. However, if we couldn't // get the trust info for some reason, set the // verification status to the error we got while // getting the trust info and bail out. // if ( !NT_SUCCESS(Status) ) { VerificationStatus = Status; Status = STATUS_SUCCESS; goto Cleanup; } // // If the trusted side returned trust attributes, // check if trust attributes match. // // The first ULONG in the trust info is the trust attributes // if ( TrustInfo != NULL && TrustInfo->UlongEntryCount > NL_GENERIC_RPC_TRUST_ATTRIB_INDEX ) { // // We are only interested in the forest transitive bit // if ( (ClientSession->CsTrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0 ) { if ( (TrustInfo->UlongData[NL_GENERIC_RPC_TRUST_ATTRIB_INDEX] & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) == 0 ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlVerifyTrust: F bit is set locally but not on trusted side\n" )); VerificationStatus = STATUS_DOMAIN_TRUST_INCONSISTENT; goto Cleanup; } } else { if ( (TrustInfo->UlongData[NL_GENERIC_RPC_TRUST_ATTRIB_INDEX] & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0 ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlVerifyTrust: F bit is set on trusted side but not locally\n" )); VerificationStatus = STATUS_DOMAIN_TRUST_INCONSISTENT; goto Cleanup; } } TrustAttribVerified = TRUE; } // // OK, the trust attributes check succeeded. // Proceed to check the password match // // Get our local passwords // Status = NlGetOutgoingPassword( ClientSession, &OurNewPassword, &OurOldPassword, &DummyPasswordVersionNumber, NULL ); // No need to return password set time if ( !NT_SUCCESS(Status) ) { goto Cleanup; } // // Check if our new password matches // either one returned from the trusted side // if ( OurNewPassword != NULL ) { Status = RtlCalculateNtOwfPassword( OurNewPassword, &OurNewOwfPassword ); if ( !NT_SUCCESS( Status ) ) { // // return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } goto Cleanup; } // // Check if this password is the same as the new one from trusted side // if ( RtlEqualNtOwfPassword(&OurNewOwfPassword, &NewOwfPassword) ) { NlPrintCs(( NL_MISC, ClientSession, "NlVerifyTrust: new-new password match (%s trust attributes)\n", (TrustAttribVerified ? "with" : "without") )); VerificationStatus = STATUS_SUCCESS; goto Cleanup; } // // Check if this password is the same as the old one from trusted side // if ( RtlEqualNtOwfPassword(&OurNewOwfPassword, &OldOwfPassword) ) { NlPrintCs(( NL_MISC, ClientSession, "NlVerifyTrust: new-old password match (%s trust attributes)\n", (TrustAttribVerified ? "with" : "without") )); VerificationStatus = STATUS_SUCCESS; goto Cleanup; } } // // Check if our old password matches // either one returned from the trusted side // if ( OurOldPassword != NULL ) { Status = RtlCalculateNtOwfPassword( OurOldPassword, &OurOldOwfPassword ); if ( !NT_SUCCESS( Status ) ) { // // return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } goto Cleanup; } // // Check if this password is the same as the new one from trusted side // if ( RtlEqualNtOwfPassword(&OurOldOwfPassword, &NewOwfPassword) ) { NlPrintCs(( NL_MISC, ClientSession, "NlVerifyTrust: old-new password match (%s trust attributes)\n", (TrustAttribVerified ? "with" : "without") )); VerificationStatus = STATUS_SUCCESS; goto Cleanup; } // // Check if this password is the same as the old one from trusted side // if ( RtlEqualNtOwfPassword(&OurOldOwfPassword, &OldOwfPassword) ) { NlPrintCs(( NL_MISC, ClientSession, "NlVerifyTrust: old-old password match (%s trust attributes)\n", (TrustAttribVerified ? "with" : "without") )); VerificationStatus = STATUS_SUCCESS; goto Cleanup; } } // // If we are here, passwords didn't match // VerificationStatus = STATUS_WRONG_PASSWORD; NlPrintCs(( NL_CRITICAL, ClientSession, "NlVerifyTrust: passwords don't match\n" )); } Cleanup: if ( AmWriter ) { NlResetWriterClientSession( ClientSession ); } // // On success, return the results of verification // if ( Status == STATUS_SUCCESS ) { // // If we don't know the server name, // set it to blank name // if ( ServerName == NULL ) { ServerName = NetpAllocWStrFromWStr( L"" ); if ( ServerName == NULL ) { Status = STATUS_NO_MEMORY; } } // // Allocate the memory for returned structure // if ( Status == STATUS_SUCCESS ) { QueryInformation->NetlogonInfo2 = MIDL_user_allocate( sizeof(NETLOGON_INFO_2) ); if ( QueryInformation->NetlogonInfo2 == NULL ) { Status = STATUS_NO_MEMORY; } } // // If allocations succeeded, // return the data // if ( Status == STATUS_SUCCESS ) { QueryInformation->NetlogonInfo2->netlog2_flags = 0; // // Indicate that we are returing the verification status // in netlog2_pdc_connection_status // QueryInformation->NetlogonInfo2->netlog2_flags |= NETLOGON_VERIFY_STATUS_RETURNED; QueryInformation->NetlogonInfo2->netlog2_pdc_connection_status = NetpNtStatusToApiStatus( VerificationStatus ); // // Return the server discovery flags // if ( ServerDiscoveryFlags & CS_DISCOVERY_HAS_TIMESERV ) { QueryInformation->NetlogonInfo2->netlog2_flags |= NETLOGON_HAS_TIMESERV; } if ( ServerDiscoveryFlags & CS_DISCOVERY_HAS_IP ) { QueryInformation->NetlogonInfo2->netlog2_flags |= NETLOGON_HAS_IP; } // // Return the current secure channel status // and the server name // QueryInformation->NetlogonInfo2->netlog2_tc_connection_status = NetpNtStatusToApiStatus( SecureChannelStatus ); QueryInformation->NetlogonInfo2->netlog2_trusted_dc_name = ServerName; ServerName = NULL; // don't free this name below } } // // Free locally used resources // if ( OurNewPassword != NULL ) { LocalFree( OurNewPassword ); } if ( OurOldPassword != NULL ) { LocalFree( OurOldPassword ); } if ( ServerName != NULL ) { NetApiBufferFree( ServerName ); } if ( TrustInfo != NULL ) { NetApiBufferFree( TrustInfo ); } if ( ValidationInformation != NULL ) { MIDL_user_free( ValidationInformation ); } return Status; } NET_API_STATUS NetrLogonControl( IN LPWSTR ServerName OPTIONAL, IN DWORD FunctionCode, IN DWORD QueryLevel, OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation ) /*++ Routine Description: This function controls various aspects of the Netlogon service. It can be used to request that a BDC ensure that its copy of the SAM database is brought up to date. It can, also, be used to determine if a BDC currently has a secure channel open to the PDC. Only an Admin, Account Operator or Server Operator may call this function. Arguments: ServerName - The name of the remote server. FunctionCode - Defines the operation to be performed. The valid values are: FunctionCode Values NETLOGON_CONTROL_QUERY - No operation. Merely returns the information requested. NETLOGON_CONTROL_REPLICATE: Forces the SAM database on a BDC to be brought in sync with the copy on the PDC. This operation does NOT imply a full synchronize. The Netlogon service will merely replicate any outstanding differences if possible. NETLOGON_CONTROL_SYNCHRONIZE: Forces a BDC to get a completely new copy of the SAM database from the PDC. This operation will perform a full synchronize. NETLOGON_CONTROL_PDC_REPLICATE: Forces a PDC to ask each BDC to replicate now. QueryLevel - Indicates what information should be returned from the Netlogon Service. Must be 1. QueryInformation - Returns a pointer to a buffer which contains the requested information. The buffer must be freed using NetApiBufferFree. Return Value: NERR_Success: the operation was successful ERROR_NOT_SUPPORTED: Function code is not valid on the specified server. (e.g. NETLOGON_CONTROL_REPLICATE was passed to a PDC). --*/ { NET_API_STATUS NetStatus; QueryInformation->NetlogonInfo1 = NULL; switch( QueryLevel ) { case (1): break; case (2): NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; default: NetStatus = ERROR_INVALID_LEVEL; goto Cleanup; } // // ensure the input data is valid. // switch( FunctionCode ) { case NETLOGON_CONTROL_QUERY: case NETLOGON_CONTROL_REPLICATE: case NETLOGON_CONTROL_SYNCHRONIZE: case NETLOGON_CONTROL_PDC_REPLICATE: #if NETLOGONDBG case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: case NETLOGON_CONTROL_TRUNCATE_LOG: case NETLOGON_CONTROL_BREAKPOINT: #endif // NETLOGONDBG break; default: NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } NetStatus = NetrLogonControl2Ex( ServerName, FunctionCode, QueryLevel, NULL, QueryInformation ); Cleanup: return( NetStatus ); } NET_API_STATUS NetrLogonControl2( IN LPWSTR ServerName OPTIONAL, IN DWORD FunctionCode, IN DWORD QueryLevel, IN PNETLOGON_CONTROL_DATA_INFORMATION InputData, OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation ) /*++ Routine Description: Same as NetrLogonControl2Ex. A client should never pass a QueryLevel of 4 to this procedure. We don't check since, if they did, it's too late now. The client will access violate upon return. Arguments: Same as NetrLogonControl2Ex. Return Value: --*/ { NET_API_STATUS NetStatus; NetStatus = NetrLogonControl2Ex( ServerName, FunctionCode, QueryLevel, InputData, QueryInformation ); return NetStatus; } NET_API_STATUS NetrLogonControl2Ex( IN LPWSTR ServerName OPTIONAL, IN DWORD FunctionCode, IN DWORD QueryLevel, IN PNETLOGON_CONTROL_DATA_INFORMATION InputData, OUT PNETLOGON_CONTROL_QUERY_INFORMATION QueryInformation ) /*++ Routine Description: This function controls various aspects of the Netlogon service. It can be used to request that a BDC ensure that its copy of the SAM database is brought up to date. It can, also, be used to determine if a BDC currently has a secure channel open to the PDC. Only an Admin, Account Operator or Server Operator may call this function. Arguments: ServerName - The name of the remote server. FunctionCode - Defines the operation to be performed. The valid values are: FunctionCode Values NETLOGON_CONTROL_QUERY - No operation. Merely returns the information requested. NETLOGON_CONTROL_REPLICATE: Forces the SAM database on a BDC to be brought in sync with the copy on the PDC. This operation does NOT imply a full synchronize. The Netlogon service will merely replicate any outstanding differences if possible. NETLOGON_CONTROL_SYNCHRONIZE: Forces a BDC to get a completely new copy of the SAM database from the PDC. This operation will perform a full synchronize. NETLOGON_CONTROL_PDC_REPLICATE: Forces a PDC to ask each BDC to replicate now. NETLOGON_CONTROL_REDISCOVER: Forces a DC to rediscover the specified trusted domain DC. NETLOGON_CONTROL_TC_QUERY: Query the status of the specified trusted domain secure channel. NETLOGON_CONTROL_TC_VERIFY: Verify the status of the specified trusted domain secure channel. If the current status is success (which means that the last operation performed over the secure channel was successful), ping the DC. If the current status is not success or the ping fails, rediscover a new DC. NETLOGON_CONTROL_TRANSPORT_NOTIFY: Notifies netlogon that a new transport has been added. Currently, it merely resets discovery timeouts allowing all secure channel discoveries to be retried immediately. However, the intention is to later add support for anything similar. The intention is that a client can call this function after a new transport has been added (e.g., it dialed a RAS link) and immediately before calling Netlogon (e.g., indirectly by doing an LsaLogonUser). NETLOGON_CONTROL_FORCE_DNS_REG: Forces the DC to re-register all of its DNS records. QueryLevel parameter must be 1. NETLOGON_CONTROL_QUERY_DNS_REG: Query the status of DNS updates performed by netlogon. If there was any DNS registration or deregistration error for any of the records as they were updated last time, the query result will be negative; otherwise the query result will be positive. QueryLevel parameter must be 1. QueryLevel - Indicates what information should be returned from the Netlogon Service. InputData - According to the function code specified this parameter will carry input data. NETLOGON_CONTROL_REDISCOVER, NETLOGON_CONTROL_TC_QUERY, and NETLOGON_CONTROL_TC_VERIFY function code specify the trusted domain name (LPWSTR type) here. NETLOGON_CONTROL_FIND_USER function code specifies the user name (LPWSTR type) here. QueryInformation - Returns a pointer to a buffer which contains the requested information. The buffer must be freed using NetApiBufferFree. Return Value: NERR_Success: the operation was successful ERROR_NOT_SUPPORTED: Function code is not valid on the specified server. (e.g. NETLOGON_CONTROL_REPLICATE was passed to a PDC). --*/ { NET_API_STATUS NetStatus; NTSTATUS Status; DWORD i; DWORD InfoSize; BOOL DnsLastStatusCheck = TRUE; ACCESS_MASK DesiredAccess; UNICODE_STRING DomainName; PDOMAIN_INFO DomainInfo = NULL; PCLIENT_SESSION ClientSession = NULL; LPWSTR TDCName = NULL; LPWSTR TrustedDomainName = NULL; LPWSTR SamAccountName = NULL; LPWSTR SamDomainName = NULL; DWORD SamExtraFlags; DWORD TcServerDiscoveryFlags = 0; PNL_DC_CACHE_ENTRY DcCacheEntry = NULL; // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( ServerName ); if ( DomainInfo == NULL ) { NetStatus = ERROR_INVALID_COMPUTERNAME; goto Cleanup; } // // Ensure the QueryLevel is valid // QueryInformation->NetlogonInfo1 = NULL; switch( QueryLevel ) { case (1): case (2): case (3): case (4): break; default: NetStatus = ERROR_INVALID_LEVEL; goto Cleanup; } // // ensure the input data is valid. // switch( FunctionCode ) { case NETLOGON_CONTROL_REDISCOVER: case NETLOGON_CONTROL_TC_QUERY: case NETLOGON_CONTROL_TC_VERIFY: case NETLOGON_CONTROL_FIND_USER: case NETLOGON_CONTROL_CHANGE_PASSWORD: #if NETLOGONDBG case NETLOGON_CONTROL_SET_DBFLAG: #endif // NETLOGONDBG NlAssert( InputData != NULL ); if( InputData == NULL ) { NetStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } break; default: break; } // // compute access mask. // switch ( FunctionCode ) { case NETLOGON_CONTROL_QUERY: case NETLOGON_CONTROL_TC_QUERY: case NETLOGON_CONTROL_TRANSPORT_NOTIFY: case NETLOGON_CONTROL_QUERY_DNS_REG: DesiredAccess = NETLOGON_QUERY_ACCESS; break; case NETLOGON_CONTROL_REPLICATE: case NETLOGON_CONTROL_SYNCHRONIZE: case NETLOGON_CONTROL_PDC_REPLICATE: case NETLOGON_CONTROL_REDISCOVER: case NETLOGON_CONTROL_TC_VERIFY: case NETLOGON_CONTROL_FIND_USER: case NETLOGON_CONTROL_CHANGE_PASSWORD: case NETLOGON_CONTROL_FORCE_DNS_REG: #if NETLOGONDBG case NETLOGON_CONTROL_BREAKPOINT: case NETLOGON_CONTROL_SET_DBFLAG: case NETLOGON_CONTROL_TRUNCATE_LOG: case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: #endif // NETLOGONDBG default: DesiredAccess = NETLOGON_CONTROL_ACCESS; break; } // // Perform access validation on the caller. // NetStatus = NetpAccessCheck( NlGlobalNetlogonSecurityDescriptor, // Security descriptor DesiredAccess, // Desired access &NlGlobalNetlogonInfoMapping ); // Generic mapping if ( NetStatus != NERR_Success) { NetStatus = ERROR_ACCESS_DENIED; goto Cleanup; } // // Handle the various FunctionCodes // switch ( FunctionCode ) { // // On a query, do nothing but return status. // case NETLOGON_CONTROL_QUERY: NlPrintDom((NL_MISC, DomainInfo, "QUERY function received.\n")); break; #ifdef _DC_NETLOGON // // Force a PDC to broadcast a database change record. // case NETLOGON_CONTROL_PDC_REPLICATE: NlPrint((NL_SYNC, "PDC REPLICATE function received.\n" )); #if 0 { NlSitesUpdateSiteCoverage( DomainInfo, NULL ); NlPrintDom((NL_CRITICAL, DomainInfo, "Cliffs test code *****************************.\n" )); } #endif // // This FunctionCode is only valid on a PDC // if ( !NlGlobalPdcDoReplication ) { NlPrint((NL_CRITICAL, "PDC REPLICATE only supported in mixed mode.\n" )); NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } // // Simply send the announcement. Any BDC that is out of date // will replicate any changes. // NlPrimaryAnnouncement( ANNOUNCE_FORCE ); break; #endif // _DC_NETLOGON // // Force to rediscover trusted domain DCs. // case NETLOGON_CONTROL_REDISCOVER: { LPWSTR DiscoveredDc; NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NETLOGON_CONTROL_REDISCOVER function received.\n")); NlAssert( InputData->TrustedDomainName != NULL ); if( InputData->TrustedDomainName == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl called with function code NETLOGON_CONTROL_REDISCOVER " "specified NULL trusted domain name. \n")); NetStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } // // Determine if there is a \ in the passed name. // If so, truncate the string there and save a pointer to the // DC name after the \. // DiscoveredDc = wcschr( InputData->TrustedDomainName, L'\\' ); if ( DiscoveredDc != NULL ) { *DiscoveredDc = L'\0'; DiscoveredDc++; } RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName ); // // get client structure for the specified domain. // ClientSession = NlFindNamedClientSession( DomainInfo, &DomainName, NL_DIRECT_TRUST_REQUIRED, NULL ); if( ClientSession == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl can't find the client structure of the domain %wZ specified.\n", &DomainName )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Ping the DC to figure out if it is available // if ( DiscoveredDc != NULL ) { // // We ensure that the DC has our account. // Otherwise, if this DC doesn't have our // account, the session setup may rediscover // a different DC and we may end up setting // up the secure channel to a DC other than // the one passed to us. // NetStatus = NlPingDcName( ClientSession, 0, // Try both ping mechanisms TRUE, // Cache this DC FALSE, // Do not require IP TRUE, // Ensure the DC has our account FALSE, // Do not refresh the session DiscoveredDc, // Ping this DC &DcCacheEntry ); if ( NetStatus != NO_ERROR ) { NlPrintCs((NL_SESSION_SETUP, ClientSession, "NetrLogonControl: Unsuccessful response from DC %ws 0x%lx\n", DiscoveredDc, NetStatus )); // // If the service is paused on the server, return the // appropriate status. Otherwise, map the status // to a generic error code. // if ( NetStatus != ERROR_SERVICE_NOT_ACTIVE ) { NetStatus = ERROR_NO_LOGON_SERVERS; } goto Cleanup; } else { NlPrintCs((NL_SESSION_SETUP, ClientSession, "NetrLogonControl: Successful response from DC %ws\n", DiscoveredDc )); } } // // Force Discovery of a DC // if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl2: Can't become writer of client session.\n")); NetStatus = ERROR_NO_LOGON_SERVERS; goto Cleanup; } else { // // Reset the current DC. // NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS ); // // If the caller specified a DC, // set it in the client sesion structure. // if ( DcCacheEntry != NULL ) { NlSetServerClientSession( ClientSession, DcCacheEntry, TRUE, // was discovery with account FALSE ); // not the session refresh } // // Setup a session to the DC (Discover one if needed) // Status = NlSessionSetup( ClientSession ); NlResetWriterClientSession( ClientSession ); if ( !NT_SUCCESS(Status) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NetrLogonControl: Discovery failed %lx\n", Status )); NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } } break; } case NETLOGON_CONTROL_TC_QUERY: NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NETLOGON_CONTROL_TC_QUERY function received.\n")); NlAssert( InputData->TrustedDomainName != NULL ); if( InputData->TrustedDomainName == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl called with NETLOGON_CONTROL_TC_QUERY " "and specified NULL trusted domain name. \n")); NetStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName ); // // get client structure for the specified domain. // ClientSession = NlFindNamedClientSession( DomainInfo, &DomainName, NL_DIRECT_TRUST_REQUIRED, NULL ); if( ClientSession == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl can't find the client structure of the domain %wZ specified.\n", &DomainName )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } break; case NETLOGON_CONTROL_TC_VERIFY: NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NETLOGON_CONTROL_TC_VERIFY function received.\n")); NlAssert( InputData->TrustedDomainName != NULL ); if( InputData->TrustedDomainName == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl called with NETLOGON_CONTROL_TC_VERIFY " "and specified NULL trusted domain name. \n")); NetStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } // // This requires query level 2 // if ( QueryLevel != 2 ) { NetStatus = ERROR_INVALID_LEVEL; goto Cleanup; } RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName ); // // get client structure for the specified domain. // ClientSession = NlFindNamedClientSession( DomainInfo, &DomainName, NL_DIRECT_TRUST_REQUIRED, NULL ); if( ClientSession == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl can't find the client structure of the domain %wZ specified.\n", &DomainName )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Verify the trust // Status = NlVerifyTrust( ClientSession, QueryInformation ); // // NlVerifyTrust built all the required info, // so we are done // NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; case NETLOGON_CONTROL_CHANGE_PASSWORD: NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NETLOGON_CONTROL_CHANGE_PASSWORD function received.\n")); // NlAssert( InputData->TrustedDomainName != NULL ); if( InputData->TrustedDomainName == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl called with NETLOGON_CONTROL_CHANGE_PASSWORD " "and specified NULL trusted domain name. \n")); NetStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } RtlInitUnicodeString(&DomainName, InputData->TrustedDomainName ); // // get client structure for the specified domain. // ClientSession = NlFindNamedClientSession( DomainInfo, &DomainName, NL_DIRECT_TRUST_REQUIRED | NL_ROLE_PRIMARY_OK, NULL ); if( ClientSession == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl can't find the client structure of the domain %wZ specified.\n", &DomainName )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Do not allow password change for an interdomain trust account on a BDC // if ( (DomainInfo->DomRole == RoleBackup) && ( IsDomainSecureChannelType(ClientSession->CsSecureChannelType )) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl called with NETLOGON_CONTROL_CHANGE_PASSWORD " "for an interdomain trust account on a BDC. \n")); NetStatus = ERROR_INVALID_DOMAIN_ROLE; goto Cleanup; } // // Force the password on the client session found // Status = NlChangePassword( ClientSession, TRUE, NULL ); if ( !NT_SUCCESS(Status) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NetrLogonControl: Password Change failed %lx\n", Status )); NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } break; // // A client has added a new transport and needs us to use it. // Mark all the client sessions that its OK to authentication NOW. // case NETLOGON_CONTROL_TRANSPORT_NOTIFY: { NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_TRANSPORT_NOTIFY function received.\n" )); // // Flush any caches that aren't valid any more since there // is now a new transport // NlFlushCacheOnPnp(); break; } // // Find a user in one of the trusted domains. // case NETLOGON_CONTROL_FIND_USER: { UNICODE_STRING UserNameString; NlPrint((NL_MISC, "NETLOGON_CONTROL_FIND_USER function received for %ws.\n", InputData->UserName )); if ( QueryLevel != 4 ) { NetStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } // // Don't allow on workstation since CrackSingleName isn't implemented on // a workstation. // if ( NlGlobalMemberWorkstation ) { NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } // // Find a user in one of the trusted domains. // // Allow machine accounts just as a handy extension. // Don't find "Local User" accounts since we can't pass through to them // RtlInitUnicodeString( &UserNameString, InputData->UserName ); Status = NlPickDomainWithAccount ( DomainInfo, &UserNameString, NULL, // No domain name USER_NORMAL_ACCOUNT | USER_MACHINE_ACCOUNT_MASK, NullSecureChannel, // No incoming secure channel FALSE, // Call wasn't expedited to root FALSE, // Call wasn't first hop across forest. &SamAccountName, &SamDomainName, &SamExtraFlags ); if ( !NT_SUCCESS( Status )) { if ( Status == STATUS_NO_SUCH_DOMAIN ) { NetStatus = NERR_UserNotFound; } else { NetStatus = NetpNtStatusToApiStatus( Status ); } goto Cleanup; } // // If the account isn't in this forest, // tell the caller. // if ( SamExtraFlags & (NL_EXFLAGS_EXPEDITE_TO_ROOT|NL_EXFLAGS_CROSS_FOREST_HOP) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl: User %ws is in a trusted forest (%lx).\n", InputData->UserName, SamExtraFlags )); NetStatus = NERR_UserNotFound; goto Cleanup; } } break; // // Force re-registration of all DNS records for this DC // case NETLOGON_CONTROL_FORCE_DNS_REG: // // This is not supported on workstations // if ( NlGlobalMemberWorkstation ) { NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } // // Re-register all records // NlDnsPnp( TRUE ); break; // // Query the status of last DNS updates // case NETLOGON_CONTROL_QUERY_DNS_REG: // // This is not supported on workstations // if ( NlGlobalMemberWorkstation ) { NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } // // This requires query level 1 // if ( QueryLevel != 1 ) { NetStatus = ERROR_INVALID_LEVEL; goto Cleanup; } // // Call the worker // DnsLastStatusCheck = NlDnsCheckLastStatus(); break; #if NETLOGONDBG // // Force a breakpoint // case NETLOGON_CONTROL_BREAKPOINT: KdPrint(( "I_NetLogonControl Break Point\n")); #if DBG DbgBreakPoint(); #else // DBG NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; #endif // DBG break; // // Change the debug flags // case NETLOGON_CONTROL_SET_DBFLAG: NlGlobalParameters.DbFlag = InputData->DebugFlag; NlPrint((NL_MISC,"DbFlag is set to %lx\n", NlGlobalParameters.DbFlag )); break; // // Truncate the log file // case NETLOGON_CONTROL_TRUNCATE_LOG: NlOpenDebugFile( TRUE ); NlPrint((NL_MISC, "TRUNCATE_LOG function received.\n" )); break; // // Backup changelog file // case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: NlPrint((NL_MISC, "BACKUP_CHANGE_LOG function received, (%ld).\n", NetStatus )); NetStatus = NlBackupChangeLogFile(); break; #if DBG // // Unload Netlogon.dll // case NETLOGON_CONTROL_UNLOAD_NETLOGON_DLL: // // Don't unload the DLL now. // RPC still needs the the DLL as a security provider throughout shutdown. // NlPrint((NL_MISC, "UNLOAD_NETLOGON_DLL function received.\n" )); NlGlobalUnloadNetlogon = TRUE; NetStatus = NO_ERROR; break; #endif // DBG #endif // NETLOGONDBG // // All other function codes are invalid. // default: NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } // // allocate return info structure. // switch( QueryLevel ) { case (1): InfoSize = sizeof(NETLOGON_INFO_1); break; case (2): InfoSize = sizeof(NETLOGON_INFO_2); break; case (3): InfoSize = sizeof(NETLOGON_INFO_3); break; case (4): InfoSize = sizeof(NETLOGON_INFO_4); break; } QueryInformation->NetlogonInfo1 = MIDL_user_allocate( InfoSize ); if ( QueryInformation->NetlogonInfo1 == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } // // Return DomainName and DC Name. // switch( QueryLevel ) { case (4): switch ( FunctionCode ) { case NETLOGON_CONTROL_FIND_USER: { UNICODE_STRING SamDomainNameString; // // If the account is in this Domain, // return the name of this DC. // RtlInitUnicodeString( &SamDomainNameString, SamDomainName ); if ( RtlEqualDomainName( &SamDomainNameString, &DomainInfo->DomUnicodeDomainNameString ) || NlEqualDnsNameU( &SamDomainNameString, &DomainInfo->DomUnicodeDnsDomainNameString ) ) { // // Grab the name of this DC. // TDCName = NetpAllocWStrFromWStr( DomainInfo->DomUncUnicodeComputerName ); if ( TDCName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } // // Grab the name of this domain. // LOCK_TRUST_LIST( DomainInfo ); TrustedDomainName = NetpAllocWStrFromWStr( DomainInfo->DomUnicodeDomainName ); UNLOCK_TRUST_LIST( DomainInfo ); if ( TrustedDomainName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } // // If the account is not in this domain, // find the client session and get the DC name from there. // } else { // // Find the client session of the Domain anywhere in the forest. // ClientSession = NlFindNamedClientSession( DomainInfo, &SamDomainNameString, 0, // Indirect trust OK NULL ); if ( ClientSession == NULL ) { // // Replication latency. The GC knows of a domain in the forest // that we don't trust. // NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonControl: User %ws\\%ws apparently isn't in this forest.\n", SamAccountName, SamDomainName )); NetStatus = NERR_UserNotFound; goto Cleanup; } // // If the account isn't on a directly trusted domain, // indicate that we don't know the DC. // if ( (ClientSession->CsFlags & CS_DIRECT_TRUST) == 0 ) { TDCName = NULL; // // If the account is on a directly trusted domain, // return the name of a DC in the domain. // // Capture the name of the server // (even if it is an empty string.) // } else { // REVIEW: do discovery here. Status = NlCaptureServerClientSession( ClientSession, &TDCName, NULL ); if ( !NT_SUCCESS( Status )) { TDCName = NetpAllocWStrFromWStr( L"" ); } if ( TDCName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } } // // Capture the name of the domain. // if ( ClientSession->CsDebugDomainName != NULL ) { TrustedDomainName = NetpAllocWStrFromWStr( ClientSession->CsDebugDomainName ); if ( TrustedDomainName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } } else { TrustedDomainName = NULL; } } QueryInformation->NetlogonInfo4->netlog4_trusted_dc_name = TDCName; QueryInformation->NetlogonInfo4->netlog4_trusted_domain_name = TrustedDomainName; break; } default: NetStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } break; // // Return queried profile information. // case (3): QueryInformation->NetlogonInfo3->netlog3_flags = 0; QueryInformation->NetlogonInfo3->netlog3_logon_attempts = // ??: What about kerberos logons MsvGetLogonAttemptCount(); QueryInformation->NetlogonInfo3->netlog3_reserved1 = 0; QueryInformation->NetlogonInfo3->netlog3_reserved2 = 0; QueryInformation->NetlogonInfo3->netlog3_reserved3 = 0; QueryInformation->NetlogonInfo3->netlog3_reserved4 = 0; QueryInformation->NetlogonInfo3->netlog3_reserved5 = 0; break; // // Return secure channel specific information. // case (2): switch ( FunctionCode ) { case NETLOGON_CONTROL_REDISCOVER: case NETLOGON_CONTROL_TC_QUERY: case NETLOGON_CONTROL_TC_VERIFY: if( ClientSession == NULL ) { NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Capture the name of the server // (even if it is an empty string.) // Status = NlCaptureServerClientSession( ClientSession, &TDCName, &TcServerDiscoveryFlags ); QueryInformation->NetlogonInfo2->netlog2_tc_connection_status = NetpNtStatusToApiStatus(Status); if ( !NT_SUCCESS( Status )) { TDCName = NetpAllocWStrFromWStr( L"" ); } if ( TDCName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } QueryInformation->NetlogonInfo2->netlog2_trusted_dc_name = TDCName; break; default: NetStatus = ERROR_INVALID_PARAMETER; goto Cleanup; } // // fall through to fill other fields of the info structure. // // // Return status of secure channel to PDC. // case (1): // // Fill in the return buffer // QueryInformation->NetlogonInfo1->netlog1_flags = 0; if ( TcServerDiscoveryFlags & CS_DISCOVERY_HAS_TIMESERV ) { QueryInformation->NetlogonInfo1->netlog1_flags |= NETLOGON_HAS_TIMESERV; } if ( TcServerDiscoveryFlags & CS_DISCOVERY_HAS_IP ) { QueryInformation->NetlogonInfo1->netlog1_flags |= NETLOGON_HAS_IP; } if ( !DnsLastStatusCheck ) { QueryInformation->NetlogonInfo1->netlog1_flags |= NETLOGON_DNS_UPDATE_FAILURE; } if ( DomainInfo->DomRole == RolePrimary ) { QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status = NERR_Success; } else { PCLIENT_SESSION LocalClientSession; LocalClientSession = NlRefDomClientSession( DomainInfo ); if ( LocalClientSession != NULL ) { QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status = NetpNtStatusToApiStatus( LocalClientSession->CsConnectionStatus); NlUnrefClientSession( LocalClientSession ); } else { QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status = ERROR_NOT_SUPPORTED; } } break; default: break; } NetStatus = NERR_Success; // // Free up locally used resources. // Cleanup: if( ClientSession != NULL ) { NlUnrefClientSession( ClientSession ); } if ( SamAccountName != NULL ) { NetApiBufferFree( SamAccountName ); } if ( SamDomainName != NULL ) { NetApiBufferFree( SamDomainName ); } if ( NetStatus != NERR_Success ) { if ( QueryInformation->NetlogonInfo1 != NULL ) { MIDL_user_free( QueryInformation->NetlogonInfo1 ); QueryInformation->NetlogonInfo1 = NULL; } if ( TDCName != NULL ) { MIDL_user_free( TDCName ); } if ( TrustedDomainName != NULL ) { MIDL_user_free( TrustedDomainName ); } } if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } if ( DcCacheEntry != NULL ) { NetpDcDerefCacheEntry( DcCacheEntry ); } return NetStatus; } VOID NlFreePingContext( IN PNL_GETDC_CONTEXT PingContext ) /*++ Routine Description: Free the context used to perform DC pings. Arguments: PingContext - Context used to perform the pings. --*/ { if ( PingContext != NULL ) { NetpDcUninitializeContext( PingContext ); NetApiBufferFree( PingContext ); } } NET_API_STATUS NlPingDcName ( IN PCLIENT_SESSION ClientSession, IN ULONG DcNamePingFlags, IN BOOL CachePingedDc, IN BOOL RequireIp, IN BOOL DoPingWithAccount, IN BOOL RefreshClientSession, IN LPWSTR DcName OPTIONAL, OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry OPTIONAL ) /*++ Routine Description: Ping the specified DC using the appropriate ping mechanism. If RefreshClientSession is TRUE, the caller must be the writer of the passed client session. Arguments: ClientSession - The client session to ping a DC for. If DcName is NULL, the server from ClientSession is pinged. If DcName isn't NULL, ClientSession is used to get the session info (other than the server name) needed to perform the pings. DcNamePingFlags - Specifies properties of DcName. Can be DS_PING_NETBIOS_HOST or DS_PING_NETBIOS_HOST or zero. If other than zero, only the specified ping mechanism will be used. CachePingedDc - If TRUE, the successfully pinged DC will be cached for future use by DsGetDcName. RequireIp - TRUE if pinging the DC must be done using only IP enabled transports. DoPingWithAccount - If TRUE, the account name for this machine will be specified in the pings. RefreshClientSession - If TRUE, the client session will be refreshed using the ping response info. If TRUE, the caller must be the writer of the client session. DcName - If set, that DC name will be pinged using info from ClientSession. NlDcCacheEntry - Returns the data structure describing response received from the DC. Should be freed by calling NetpDcDerefCacheEntry. Return Value: NO_ERROR - Success. ERROR_NO_LOGON_SERVERS - No DC could be found ERROR_NO_SUCH_USER - Returned if we do ping with account and the DC doesn't have the account specified. ERROR_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper domain controller of the specified domain. ERROR_SERVICE_NOT_ACTIVE - The netlogon service is paused on the server. --*/ { NTSTATUS Status; NET_API_STATUS NetStatus; LPWSTR LocalDcName = NULL; LPWSTR CapturedDnsForestName = NULL; ULONG AllowableAccountControlBits; NL_GETDC_CONTEXT Context; BOOL TriedContextInitialization = FALSE; ULONG Flags = 0; ULONG InternalFlags = 0; PDNS_RECORD DnsRecords = NULL; LPSOCKET_ADDRESS SockAddresses = NULL; LPSOCKET_ADDRESS AllocatedSockAddresses = NULL; SOCKET_ADDRESS OneSockAddress; SOCKADDR_IN OneSockAddressIn; ULONG SockAddressCount = 0; ULONG LoopIndex; PNL_DC_CACHE_ENTRY LocalNlDcCacheEntry = NULL; // // If a DC name specified, try to figure out its properties // if ( DcName != NULL ) { // // Prove below that the name is valid // NetStatus = ERROR_INVALID_COMPUTERNAME; // // If the DC name is a syntactically valid DNS name, // assume the server name is a DNS name. // // Skip this step if we are told that the name is Netbios. // if ( (DcNamePingFlags & DS_PING_NETBIOS_HOST) == 0 && NetpDcValidDnsDomain(DcName) ) { NetStatus = NO_ERROR; // // Get the IP address of the server from DNS // NetStatus = DnsQuery_W( DcName, DNS_TYPE_A, 0, NULL, // No list of DNS servers &DnsRecords, NULL ); // // On success, set to use ldap pings. Otherwise, do not // error out, rather try the mailslot mechanism // if ( NetStatus == NO_ERROR ) { NetStatus = NetpSrvProcessARecords( DnsRecords, NULL, 0, &SockAddressCount, &AllocatedSockAddresses ); if ( NetStatus == NO_ERROR && SockAddressCount > 0 ) { SockAddresses = AllocatedSockAddresses; InternalFlags |= DS_PING_USING_LDAP; InternalFlags |= DS_PING_DNS_HOST; } } } // // If the DC name is syntactically valid Netbios name, // assume you can use mailslot pings // // Skip this step if we are told that the name is DNS. // if ( (DcNamePingFlags & DS_PING_DNS_HOST) == 0 && NetpIsComputerNameValid(DcName) && wcslen(DcName) <= CNLEN ) { // NetpIsComputerNameValid doesn't require 15 chacter limit NetStatus = NO_ERROR; InternalFlags |= DS_PING_USING_MAILSLOT; InternalFlags |= DS_PING_NETBIOS_HOST; } // // If there is no ping mechanism to use, error out // if ( (InternalFlags & (DS_PING_USING_LDAP|DS_PING_USING_MAILSLOT)) == 0 ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlPingDcName: No ping mechanism for %ws 0x%lx\n", DcName, NetStatus )); NetStatus = ERROR_NO_LOGON_SERVERS; goto Cleanup; } LocalDcName = DcName; } // // If this is a client session to a domain in our // forest (always the case on workstation), // get our forest name. // if ( NlGlobalMemberWorkstation || (ClientSession->CsFlags & CS_DOMAIN_IN_FOREST) != 0 ) { CapturedDnsForestName = LocalAlloc( LMEM_ZEROINIT, (NL_MAX_DNS_LENGTH+1)*sizeof(WCHAR) ); if ( CapturedDnsForestName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } NlCaptureDnsForestName( CapturedDnsForestName ); } // // Capture the needed info from the Client session // EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); // // Capture the server name if the one wasn't passed. // If the client session is idle, this will fail. // if ( DcName == NULL ) { ULONG DiscoveryFlags = 0; Status = NlCaptureServerClientSession( ClientSession, &LocalDcName, &DiscoveryFlags ); if ( !NT_SUCCESS(Status) ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlPingDcName: Cannot NlCaptureServerClientSession %ld\n", Status )); if ( Status == STATUS_NO_MEMORY ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; } else { NetStatus = ERROR_NO_LOGON_SERVERS; } LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); goto Cleanup; } if ( DiscoveryFlags & CS_DISCOVERY_DNS_SERVER ) { InternalFlags |= DS_PING_DNS_HOST; } else { InternalFlags |= DS_PING_NETBIOS_HOST; } if ( DiscoveryFlags & CS_DISCOVERY_USE_LDAP ) { InternalFlags |= DS_PING_USING_LDAP; // // Capture the cached server socket address // NlAssert( ClientSession->CsServerSockAddr.iSockaddrLength != 0 ); OneSockAddress.iSockaddrLength = ClientSession->CsServerSockAddr.iSockaddrLength; OneSockAddress.lpSockaddr = (LPSOCKADDR) &OneSockAddressIn; RtlCopyMemory( OneSockAddress.lpSockaddr, ClientSession->CsServerSockAddr.lpSockaddr, ClientSession->CsServerSockAddr.iSockaddrLength ); SockAddresses = &OneSockAddress; SockAddressCount = 1; } if ( DiscoveryFlags & CS_DISCOVERY_USE_MAILSLOT ) { InternalFlags |= DS_PING_USING_MAILSLOT; } } // // Determine the Account type we're looking for. // InternalFlags |= DS_IS_TRUSTED_DOMAIN; switch ( ClientSession->CsSecureChannelType ) { case WorkstationSecureChannel: AllowableAccountControlBits = USER_WORKSTATION_TRUST_ACCOUNT; InternalFlags |= DS_IS_PRIMARY_DOMAIN; break; case TrustedDomainSecureChannel: AllowableAccountControlBits = USER_INTERDOMAIN_TRUST_ACCOUNT; break; case TrustedDnsDomainSecureChannel: AllowableAccountControlBits = USER_DNS_DOMAIN_TRUST_ACCOUNT; break; case ServerSecureChannel: AllowableAccountControlBits = USER_SERVER_TRUST_ACCOUNT; Flags |= DS_PDC_REQUIRED; InternalFlags |= DS_IS_PRIMARY_DOMAIN; break; default: NlPrintCs(( NL_CRITICAL, ClientSession, "NlPingDcName: invalid SecureChannelType %ld\n", ClientSession->CsSecureChannelType )); NetStatus = ERROR_NO_LOGON_SERVERS; LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); goto Cleanup; } // // Indicate whether IP transport is required // if ( RequireIp ) { Flags |= DS_IP_REQUIRED; } // // Initialize the ping context. // TriedContextInitialization = TRUE; NetStatus = NetpDcInitializeContext( ClientSession->CsDomainInfo, // SendDatagramContext ClientSession->CsDomainInfo->DomUnicodeComputerNameString.Buffer, #ifdef DONT_REQUIRE_MACHINE_ACCOUNT // useful for number of trust testing NULL, #else // DONT_REQUIRE_MACHINE_ACCOUNT DoPingWithAccount ? // Specify the account name as directed ClientSession->CsAccountName : NULL, #endif // DONT_REQUIRE_MACHINE_ACCOUNT DoPingWithAccount ? // Specify the account control bits as directed AllowableAccountControlBits : 0, ClientSession->CsNetbiosDomainName.Buffer, ClientSession->CsDnsDomainName.Buffer, CapturedDnsForestName, ClientSession->CsDomainId, ClientSession->CsDomainGuid, NULL, DcName == NULL ? LocalDcName+2 : // Skip '\\' in the DC name LocalDcName, // captured from the client session SockAddresses, SockAddressCount, Flags, InternalFlags, NL_GETDC_CONTEXT_INITIALIZE_FLAGS | NL_GETDC_CONTEXT_INITIALIZE_PING, &Context ); if ( NetStatus != NO_ERROR ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlPingDcName: Cannot NetpDcInitializeContext 0x%lx\n", NetStatus )); LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); goto Cleanup; } LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); // // Ping the DC and get a response from it // // If we ping using a cached IP address, it's possible that server's // IP address changed. For this case, if we fail to ping the server // on the first try, we will refresh the address by querying DNS // and then we will retry to ping the server. // for ( LoopIndex = 0; LoopIndex < 2; LoopIndex++ ) { NET_API_STATUS TmpNetStatus; ULONG TmpSockAddressCount = 0; ULONG Index; if ( LocalNlDcCacheEntry != NULL ) { NetpDcDerefCacheEntry( LocalNlDcCacheEntry ); LocalNlDcCacheEntry = NULL; } NetStatus = NlPingDcNameWithContext( &Context, MAX_DC_RETRIES, // send 2 pings TRUE, // wait for response (NL_DC_MAX_TIMEOUT + NlGlobalParameters.ExpectedDialupDelay*1000), // timeout NULL, // Don't care which domain name matched &LocalNlDcCacheEntry ); // // If we pinged successfully or // we got hard error or // we didn't do LDAP pings or // we already did DNS queries because DC name was passed or // this is the second attempt to ping the DC, // // we are done // if ( NetStatus == NO_ERROR || NetStatus == ERROR_NOT_ENOUGH_MEMORY || SockAddresses == NULL || DcName != NULL || LoopIndex == 1 ) { break; } // // Attempt to get fresh DNS records // TmpNetStatus = DnsQuery_W( LocalDcName+2, // Skip '\\' in the DC name DNS_TYPE_A, 0, NULL, // No list of DNS servers &DnsRecords, NULL ); // // Bail on error // if ( TmpNetStatus != NO_ERROR ) { break; } // // Process fresh DNS records // TmpNetStatus = NetpSrvProcessARecords( DnsRecords, NULL, 0, // force port to be zero &TmpSockAddressCount, &AllocatedSockAddresses ); // // Bail on error // if ( TmpNetStatus != NO_ERROR || TmpSockAddressCount == 0 ) { break; } // // Check if there are new addresses we didn't try // SockAddressCount = 0; for ( Index = 0; Index < TmpSockAddressCount; Index++ ) { // // Keep this entry if it's not the one we had // if ( AllocatedSockAddresses[Index].iSockaddrLength != OneSockAddress.iSockaddrLength || !RtlEqualMemory(AllocatedSockAddresses[Index].lpSockaddr, OneSockAddress.lpSockaddr, OneSockAddress.iSockaddrLength) ) { AllocatedSockAddresses[SockAddressCount] = AllocatedSockAddresses[Index]; SockAddressCount ++; } } // // Bail if we didn't get any new addresses // if ( SockAddressCount == 0 ) { break; } // // We have new addresses. Free the current // address list and add the new addresses // NetpDcFreeAddressList( &Context ); TmpNetStatus = NetpDcProcessAddressList( &Context, LocalDcName+2, AllocatedSockAddresses, SockAddressCount, FALSE, // Don't know if site specific NULL ); // // Bail on error. Otherwise, retry pinging // given the new addresses. // if ( TmpNetStatus != NO_ERROR ) { break; } NlPrintCs(( NL_CRITICAL, ClientSession, "NlPingDcName: Retry DC ping for %lu new addresses\n", SockAddressCount )); } // // Cache this DC if asked // if ( NetStatus == NO_ERROR && CachePingedDc ) { // // Set the FORCE flag so that the old entry (if any) gets replaced // Context.QueriedFlags |= DS_FORCE_REDISCOVERY; NetpDcInsertCacheEntry( &Context, LocalNlDcCacheEntry ); NlPrintCs(( NL_MISC, ClientSession, "NlPingDcName: %ws: %ws: Caching pinged DC info for %ws\n", Context.NlDcDomainEntry->UnicodeNetbiosDomainName, Context.NlDcDomainEntry->UnicodeDnsDomainName, LocalDcName )); } // // Refresh the client session, if asked // if ( NetStatus == NO_ERROR && RefreshClientSession ) { NetStatus = NlSetServerClientSession( ClientSession, LocalNlDcCacheEntry, DoPingWithAccount, // was discovery with account? TRUE ); // session refresh } // // Return the cache entry // if ( NetStatus == NO_ERROR && NlDcCacheEntry != NULL ) { *NlDcCacheEntry = LocalNlDcCacheEntry; LocalNlDcCacheEntry = NULL; } Cleanup: if ( DnsRecords != NULL ) { DnsRecordListFree( DnsRecords, DnsFreeRecordListDeep ); } if ( AllocatedSockAddresses != NULL ) { LocalFree( AllocatedSockAddresses ); } if ( DcName == NULL && LocalDcName != NULL ) { NetApiBufferFree( LocalDcName ); } if ( TriedContextInitialization ) { NetpDcUninitializeContext( &Context ); } if ( LocalNlDcCacheEntry != NULL ) { NetpDcDerefCacheEntry( LocalNlDcCacheEntry ); } if ( CapturedDnsForestName != NULL ) { LocalFree( CapturedDnsForestName ); } return NetStatus; } NTSTATUS NlGetAnyDCName ( IN PCLIENT_SESSION ClientSession, IN BOOL RequireIp, IN BOOL EnsureDcHasOurAccount, OUT PNL_DC_CACHE_ENTRY *NlDcCacheEntry, OUT PBOOLEAN DcRediscovered ) /*++ Routine Description: Get the name of the any domain controller for a trusted domain. The domain controller found in guaranteed to have be up at one point during this API call. The machine is also guaranteed to be a DC in the domain specified. The caller of this routine should not have any locks held (it calls the LSA back in several instances). This routine may take some time to execute. The caller must be a writer of the ClientSession. Arguments: ClientSession - Structure describing the session to the domain whose DC is to be returned. RequireIp - TRUE if communication with the DC must be done using only IP enabled transports. EnsureDcHasOurAccount - If TRUE this routine will verify that the returned DC has an account for this machine. NlDcCacheEntry - Returns the data structure describing response received from the server. Should be freed by calling NetpDcDerefCacheEntry DcRediscovered - Returns whether a new DC has been discovered Return Value: STATUS_SUCCESS - Success. Buffer contains DC name prefixed by \\. STATUS_NO_LOGON_SERVERS - No DC could be found STATUS_NO_SUCH_DOMAIN - The specified domain is not a trusted domain. STATUS_NO_TRUST_LSA_SECRET - The client side of the trust relationship is broken. STATUS_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is broken or the password is broken. STATUS_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper domain controller of the specified domain. --*/ { NTSTATUS Status; NET_API_STATUS NetStatus; BOOLEAN DiscoveryDone = FALSE; PNL_DC_CACHE_ENTRY NlLocalDcCacheEntry = NULL; // // Don't give up unless we've done discovery. // do { // // If we don't currently know the name of the server, // discover one. // if ( ClientSession->CsState == CS_IDLE ) { // // If we've tried to authenticate recently, // don't bother trying again. // if ( !NlTimeToReauthenticate( ClientSession ) ) { Status = ClientSession->CsConnectionStatus; goto Cleanup; } Status = NlDiscoverDc( ClientSession, DT_Synchronous, FALSE, EnsureDcHasOurAccount ? TRUE : FALSE ); if ( !NT_SUCCESS(Status) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlGetAnyDCName: Discovery failed %lx\n", Status )); goto Cleanup; } DiscoveryDone = TRUE; } // // Try multiple times to get a response from the DC using the appropriate protocol. // if ( NlLocalDcCacheEntry != NULL ) { NetpDcDerefCacheEntry( NlLocalDcCacheEntry ); NlLocalDcCacheEntry = NULL; } NetStatus = NlPingDcName( ClientSession, 0, // Use ping mechanism specified in ClientSession FALSE, // Don't cache this DC (it is already cached) RequireIp, // Require IP as the caller said EnsureDcHasOurAccount, TRUE, // Refresh the session since we are the writer NULL, // Ping the server specified in ClientSession &NlLocalDcCacheEntry ); // // If we couldn't ping the DC when IP was required, see if we can ping it at all. // If we can't ping it at all, this DC is dead, so drop it. Otherwise, the DC is // alive (so we won't drop it), but the caller is out of luck. // if ( NetStatus == ERROR_NO_LOGON_SERVERS && RequireIp ) { NET_API_STATUS TmpNetStatus; TmpNetStatus = NlPingDcName( ClientSession, 0, // Use ping mechanism specified in ClientSession FALSE, // Don't cache this DC (it is already cached) FALSE, // Do not require IP FALSE, // Don't specify account name TRUE, // Refresh the session since we are the writer NULL, // Ping the server specified in ClientSession NULL ); // No cache entry needed // // Don't drop this DC if it's alive // if ( TmpNetStatus == NO_ERROR ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NlGetAnyDCName: IP is required but only non-IP is available for %ws\n", ClientSession->CsUncServerName )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } // // Drop through and re-discover a new DC // } if ( NetStatus == NO_ERROR ) { Status = STATUS_SUCCESS; goto Cleanup; } else { NlPrintCs(( NL_CRITICAL, ClientSession, "NlGetAnyDCName: Can't ping the DC %ws 0x%lx.\n", ClientSession->CsUncServerName, NetStatus )); } // // Drop the secure channel to force the next iteration to discover // a new dc // if ( !DiscoveryDone ) { NlPrintCs((NL_CRITICAL, ClientSession, "NlGetAnyDCName: Current DC '%ws' no longer available. (rediscover)\n", ClientSession->CsUncServerName )); NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS ); } } while ( !DiscoveryDone ); Status = STATUS_NO_LOGON_SERVERS; NlPrintCs(( NL_CRITICAL, ClientSession, "NlGetAnyDCName: Failed\n" )); // // Free any locally used resources. // Cleanup: // // Don't divulge too much to the caller. // if ( Status == STATUS_ACCESS_DENIED ) { Status = STATUS_NO_TRUST_SAM_ACCOUNT; } // // Return the DC info to the caller. // if ( Status == STATUS_SUCCESS ) { *NlDcCacheEntry = NlLocalDcCacheEntry; if ( DcRediscovered != NULL ) { *DcRediscovered = DiscoveryDone; } } else if ( NlLocalDcCacheEntry != NULL ) { NetpDcDerefCacheEntry( NlLocalDcCacheEntry ); } return Status; } NET_API_STATUS NetrGetAnyDCName ( IN LPWSTR ServerName OPTIONAL, IN LPWSTR DomainName OPTIONAL, OUT LPWSTR *Buffer ) /*++ Routine Description: Get the name of the any domain controller for a trusted domain. The domain controller found in guaranteed to have be up at one point during this API call. Arguments: ServerName - name of remote server (null for local) DomainName - name of domain (null for primary domain) Buffer - Returns a pointer to an allcated buffer containing the servername of a DC of the domain. The server name is prefixed by \\. The buffer should be deallocated using NetApiBufferFree. Return Value: ERROR_SUCCESS - Success. Buffer contains DC name prefixed by \\. ERROR_NO_LOGON_SERVERS - No DC could be found ERROR_NO_SUCH_DOMAIN - The specified domain is not a trusted domain. ERROR_NO_TRUST_LSA_SECRET - The client side of the trust relationship is broken. ERROR_NO_TRUST_SAM_ACCOUNT - The server side of the trust relationship is broken or the password is broken. ERROR_DOMAIN_TRUST_INCONSISTENT - The server that responded is not a proper domain controller of the specified domain. --*/ { NTSTATUS Status; NET_API_STATUS NetStatus; LPWSTR TmpUncServerName = NULL; UNICODE_STRING DomainNameString; PDOMAIN_INFO DomainInfo = NULL; PCLIENT_SESSION ClientSession = NULL; PNL_DC_CACHE_ENTRY NlDcCacheEntry = NULL; BOOL AmWriter = FALSE; // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( ServerName ); if ( DomainInfo == NULL ) { NetStatus = ERROR_INVALID_COMPUTERNAME; goto Cleanup; } // // Fill in the primary domain name if the caller didn't specify one. // if ( DomainName == NULL || *DomainName == L'\0' ) { RtlInitUnicodeString( &DomainNameString, DomainInfo->DomUnicodeDomainName ); } else { RtlInitUnicodeString( &DomainNameString, DomainName ); } // // On the PDC or BDC, // find the Client session for the domain. // On workstations, // find the primary domain client session. // ClientSession = NlFindNamedClientSession( DomainInfo, &DomainNameString, NL_DIRECT_TRUST_REQUIRED, NULL ); if ( ClientSession == NULL ) { NlPrintDom(( NL_CRITICAL, DomainInfo, "NetrGetAnyDCName: %ws: No such trusted domain\n", DomainName )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Become a writer of the client session. // if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NetrGetAnyDCName: Can't become writer of client session.\n")); NetStatus = ERROR_NO_LOGON_SERVERS; goto Cleanup; } AmWriter = TRUE; // // Call the internal routine to do the actual work. // // Ensure that the responding DC has our account as required by this API // Status = NlGetAnyDCName( ClientSession, FALSE, // IP is not required TRUE, // Do with-account discovery &NlDcCacheEntry, NULL ); // don't care if the DC was rediscovered if ( !NT_SUCCESS(Status) ) { NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } // // Prefer Netbios DC name for this old API // if ( NlDcCacheEntry->UnicodeNetbiosDcName != NULL ) { NetStatus = NetApiBufferAllocate( (wcslen(NlDcCacheEntry->UnicodeNetbiosDcName) + 3) * sizeof(WCHAR), &TmpUncServerName ); if ( NetStatus != NO_ERROR ) { goto Cleanup; } wcscpy( TmpUncServerName, L"\\\\" ); wcscpy( TmpUncServerName+2, NlDcCacheEntry->UnicodeNetbiosDcName ); } else { NetStatus = NetApiBufferAllocate( (wcslen(NlDcCacheEntry->UnicodeDnsHostName) + 3) * sizeof(WCHAR), &TmpUncServerName ); if ( NetStatus != NO_ERROR ) { goto Cleanup; } wcscpy( TmpUncServerName, L"\\\\" ); wcscpy( TmpUncServerName+2, NlDcCacheEntry->UnicodeDnsHostName ); } *Buffer = TmpUncServerName; NetStatus = NERR_Success; Cleanup: if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } if ( ClientSession != NULL ) { if ( AmWriter ) { NlResetWriterClientSession( ClientSession ); } NlUnrefClientSession( ClientSession ); } if ( NlDcCacheEntry != NULL ) { NetpDcDerefCacheEntry( NlDcCacheEntry ); } return NetStatus; } BOOLEAN NlAllocOneDomainInfo( IN LPWSTR DomainName OPTIONAL, IN LPWSTR DnsDomainName OPTIONAL, IN LPWSTR DnsForestName OPTIONAL, IN GUID *DomainGuid OPTIONAL, IN PSID DomainSid OPTIONAL, OUT PNETLOGON_ONE_DOMAIN_INFO OneDomainInfo ) /*++ Routine Description: This function fill in a NETLOGON_ONE_DOMAIN_INFO structure suitable for returning from an RPC server routine. Arguments: Sundry parameters to fill in. OneDomainInfo - Pointer to an already allocated structure to fill in. Return Value: TRUE - success FALSE - couldn't allocate memory --*/ { IN ULONG DomainSidSize; // Copy Netbios Domainname if ( !NlAllocStringFromWStr( DomainName, &OneDomainInfo->DomainName ) ) { return FALSE; } // Copy DNS domain name if ( !NlAllocStringFromWStr( DnsDomainName, &OneDomainInfo->DnsDomainName ) ) { return FALSE; } // Copy DNS tree name if ( !NlAllocStringFromWStr( DnsForestName, &OneDomainInfo->DnsForestName ) ) { return FALSE; } // Copy Domain GUID if ( DomainGuid != NULL ) { OneDomainInfo->DomainGuid = *DomainGuid; } // Copy DomainSid if ( DomainSid != NULL ) { DomainSidSize = RtlLengthSid( DomainSid ); OneDomainInfo->DomainSid = MIDL_user_allocate( DomainSidSize ); if ( OneDomainInfo->DomainSid == NULL ) { return FALSE; } RtlCopyMemory( OneDomainInfo->DomainSid, DomainSid, DomainSidSize ); } return TRUE; } VOID NlFreeOneDomainInfo( IN PNETLOGON_ONE_DOMAIN_INFO OneDomainInfo ) /*++ Routine Description: This function free all buffers allocated from a NETLOGON_ONE_DOMAIN_INFO structure Arguments: OneDomainInfo - Pointer to an already allocated structure to fill in. Return Value: None. --*/ { if ( OneDomainInfo->DomainName.Buffer != NULL ) { MIDL_user_free( OneDomainInfo->DomainName.Buffer ); } if ( OneDomainInfo->DnsDomainName.Buffer != NULL ) { MIDL_user_free( OneDomainInfo->DnsDomainName.Buffer ); } if ( OneDomainInfo->DnsForestName.Buffer != NULL ) { MIDL_user_free( OneDomainInfo->DnsForestName.Buffer ); } if ( OneDomainInfo->TrustExtension.Buffer != NULL ) { MIDL_user_free( OneDomainInfo->TrustExtension.Buffer ); } if ( OneDomainInfo->DomainSid != NULL ) { MIDL_user_free( OneDomainInfo->DomainSid ); } return; } NTSTATUS NetrLogonDummyRoutine1( IN LPWSTR ServerName OPTIONAL, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN DWORD QueryLevel, OUT PNETLOGON_DUMMY1 Buffer ) /*++ Routine Description: This function is never called. Arguments: Return Value: --*/ { return STATUS_NOT_IMPLEMENTED; UNREFERENCED_PARAMETER( ServerName ); UNREFERENCED_PARAMETER( ComputerName ); UNREFERENCED_PARAMETER( Authenticator ); UNREFERENCED_PARAMETER( ReturnAuthenticator ); UNREFERENCED_PARAMETER( QueryLevel ); UNREFERENCED_PARAMETER( Buffer ); } NTSTATUS NetrLogonGetDomainInfo( IN LPWSTR ServerName OPTIONAL, IN LPWSTR ComputerName, IN PNETLOGON_AUTHENTICATOR Authenticator, OUT PNETLOGON_AUTHENTICATOR ReturnAuthenticator, IN DWORD QueryLevel, PNETLOGON_WORKSTATION_INFORMATION InBuffer, OUT PNETLOGON_DOMAIN_INFORMATION OutBuffer ) /*++ Routine Description: This function is used by an NT workstation to query information about the domain it is a member of. Arguments: ServerName -- Name of the DC to retrieve the data from. ComputerName -- Name of the workstation making the call. Authenticator -- supplied by the workstation. ReturnAuthenticator -- Receives an authenticator returned by the DC. QueryLevel - Level of information to return from the DC. Valid values are: 1: Return NETLOGON_DOMAIN_INFO structure. InBuffer - Buffer to pass to DC OutBuffer - Returns a pointer to an allocated buffer containing the queried information. Return Value: STATUS_SUCCESS -- The function completed successfully. STATUS_ACCESS_DENIED -- The workstations should re-authenticate with the DC. --*/ { NTSTATUS Status; PDOMAIN_INFO DomainInfo = NULL; PSERVER_SESSION ServerSession; SESSION_INFO SessionInfo; LM_OWF_PASSWORD OwfPassword; PNETLOGON_DOMAIN_INFO NetlogonDomainInfo = NULL; PNETLOGON_LSA_POLICY_INFO NetlogonLsaPolicyInfo = NULL; PNETLOGON_LSA_POLICY_INFO OutLsaPolicy = NULL; PNETLOGON_WORKSTATION_INFO InWorkstationInfo = NULL; ULONG i; ULONG ForestTrustListCount = 0; PULONG IndexInReturnedList = NULL; LPWSTR PreviousDnsHostName = NULL; BOOLEAN DomainLocked = FALSE; BOOLEAN ClientHandlesSpn = FALSE; BOOLEAN NeedBidirectionalTrust = FALSE; PLIST_ENTRY ListEntry; LPBYTE Where; // // This API is not supported on workstations. // if ( NlGlobalMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( ServerName ); NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrLogonGetDomainInfo: %ws %ld Entered\n", ComputerName, QueryLevel )); if ( DomainInfo == NULL ) { Status = STATUS_INVALID_COMPUTER_NAME; goto Cleanup; } // // Get the Session key for this session. // LOCK_SERVER_SESSION_TABLE( DomainInfo ); ServerSession = NlFindNamedServerSession( DomainInfo, ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); Status = STATUS_ACCESS_DENIED; goto Cleanup; } SessionInfo.SessionKey = ServerSession->SsSessionKey; SessionInfo.NegotiatedFlags = ServerSession->SsNegotiatedFlags; // // now verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); goto Cleanup; } UNLOCK_SERVER_SESSION_TABLE( DomainInfo ); // // Ensure we support the query level specified. // switch ( QueryLevel ) { case NETLOGON_QUERY_DOMAIN_INFO: // // Determine the size of the buffer to allocate. // EnterCriticalSection( &NlGlobalDomainCritSect ); LOCK_TRUST_LIST( DomainInfo ); DomainLocked = TRUE; // // Allocate the buffer to return. // NetlogonDomainInfo = MIDL_user_allocate( sizeof(*NetlogonDomainInfo) ); if ( NetlogonDomainInfo == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } RtlZeroMemory( NetlogonDomainInfo, sizeof(*NetlogonDomainInfo) ); // // Tell the caller the common set of bits we support // NetlogonDomainInfo->WorkstationFlags = InBuffer->WorkstationInfo->WorkstationFlags & NL_GET_DOMAIN_INFO_SUPPORTED; ClientHandlesSpn = (NetlogonDomainInfo->WorkstationFlags & NL_CLIENT_HANDLES_SPN) != 0; NeedBidirectionalTrust = (NetlogonDomainInfo->WorkstationFlags & NL_NEED_BIDIRECTIONAL_TRUSTS) != 0; // // Copy the information into the buffer. // // Copy the description of the primary domain. // EnterCriticalSection( &NlGlobalDnsForestNameCritSect ); if ( !NlAllocOneDomainInfo( DomainInfo->DomUnicodeDomainNameString.Buffer, DomainInfo->DomUnicodeDnsDomainNameString.Buffer, NlGlobalUnicodeDnsForestName, &DomainInfo->DomDomainGuidBuffer, DomainInfo->DomAccountDomainId, &NetlogonDomainInfo->PrimaryDomain ) ) { LeaveCriticalSection( &NlGlobalDnsForestNameCritSect ); Status = STATUS_NO_MEMORY; goto Cleanup; } LeaveCriticalSection( &NlGlobalDnsForestNameCritSect ); // // Determine the length of the forest trust list to return // ForestTrustListCount = DomainInfo->DomForestTrustListCount; // // Check if need to exclude directly trusting domains // if ( !NeedBidirectionalTrust ) { ULONG Index; for ( Index=0; IndexDomForestTrustListCount; Index++ ) { if ( (DomainInfo->DomForestTrustList[Index].Flags & (DS_DOMAIN_PRIMARY|DS_DOMAIN_IN_FOREST|DS_DOMAIN_DIRECT_OUTBOUND)) == 0 ) { ForestTrustListCount--; } } } // // Copy trusted domain info // if ( ForestTrustListCount != 0 ) { PNETLOGON_ONE_DOMAIN_INFO TrustedDomainInfo; ULONG Index; ULONG ReturnedEntryIndex = 0; // // If need to exclude directly trusting domains, // allocate an array of ULONGs that will be used to keep track of the // index of a trust entry in the returned list. This is needed to // corectly set ParentIndex for entries returned. // if ( !NeedBidirectionalTrust ) { IndexInReturnedList = LocalAlloc( LMEM_ZEROINIT, DomainInfo->DomForestTrustListCount * sizeof(ULONG) ); if ( IndexInReturnedList == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } } // // Allocate a buffer for the trusted domain info. // NetlogonDomainInfo->TrustedDomains = MIDL_user_allocate( sizeof(NETLOGON_ONE_DOMAIN_INFO) * ForestTrustListCount ); if ( NetlogonDomainInfo->TrustedDomains == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } RtlZeroMemory( NetlogonDomainInfo->TrustedDomains, sizeof(NETLOGON_ONE_DOMAIN_INFO) * ForestTrustListCount ); TrustedDomainInfo = NetlogonDomainInfo->TrustedDomains; NetlogonDomainInfo->TrustedDomainCount = ForestTrustListCount; for ( Index=0; IndexDomForestTrustListCount; Index++ ) { PNL_TRUST_EXTENSION TrustExtension; // // Skip this entry if need to exclude directly trusting domains. // Otherwise, remember the index of this entry in the returned list. // if ( !NeedBidirectionalTrust ) { if ( (DomainInfo->DomForestTrustList[Index].Flags & (DS_DOMAIN_PRIMARY|DS_DOMAIN_IN_FOREST|DS_DOMAIN_DIRECT_OUTBOUND)) == 0 ) { continue; } else { IndexInReturnedList[Index] = ReturnedEntryIndex; } } // // Fill in the fixed length data // TrustExtension = MIDL_user_allocate( sizeof(NL_TRUST_EXTENSION) ); if ( TrustExtension == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } TrustExtension->Flags = DomainInfo->DomForestTrustList[Index].Flags; // // If this is a primary domain entry, determine whether it runs // in native or mixed mode // if ( (DomainInfo->DomForestTrustList[Index].Flags & DS_DOMAIN_PRIMARY) && !SamIMixedDomain( DomainInfo->DomSamServerHandle ) ) { TrustExtension->Flags |= DS_DOMAIN_NATIVE_MODE; } // // Do not leak the new DS_DOMAIN_DIRECT_INBOUND bit to an old client; // it can get confused otherwise. The new DS_DOMAIN_DIRECT_OUTBOUND // bit is just the renamed old DS_DOMAIN_DIRECT_TRUST, so leave it alone. // if ( !NeedBidirectionalTrust ) { TrustExtension->Flags &= ~DS_DOMAIN_DIRECT_INBOUND; } TrustExtension->ParentIndex = DomainInfo->DomForestTrustList[Index].ParentIndex; TrustExtension->TrustType = DomainInfo->DomForestTrustList[Index].TrustType; TrustExtension->TrustAttributes = DomainInfo->DomForestTrustList[Index].TrustAttributes; TrustedDomainInfo->TrustExtension.Buffer = (LPWSTR)TrustExtension; TrustedDomainInfo->TrustExtension.MaximumLength = TrustedDomainInfo->TrustExtension.Length = sizeof(NL_TRUST_EXTENSION); // Copy the description of the primary domain. if ( !NlAllocOneDomainInfo( DomainInfo->DomForestTrustList[Index].NetbiosDomainName, DomainInfo->DomForestTrustList[Index].DnsDomainName, NULL, // DNS Tree name not meaningfull &DomainInfo->DomForestTrustList[Index].DomainGuid, DomainInfo->DomForestTrustList[Index].DomainSid, TrustedDomainInfo ) ) { Status = STATUS_NO_MEMORY; goto Cleanup; } // Move on to the next trusted domain. TrustedDomainInfo ++; ReturnedEntryIndex ++; } // // Fix ParentIndex. If need to exclude directly trusting domains, // adjust the index to point to the appropriate entry in the // returned list. Otherwise, leave it alone. // if ( !NeedBidirectionalTrust ) { PNL_TRUST_EXTENSION TrustExtension; TrustedDomainInfo = NetlogonDomainInfo->TrustedDomains; for ( Index=0; IndexTrustExtension.Buffer; if ( (TrustExtension->Flags & DS_DOMAIN_IN_FOREST) != 0 && (TrustExtension->Flags & DS_DOMAIN_TREE_ROOT) == 0 ) { TrustExtension->ParentIndex = IndexInReturnedList[TrustExtension->ParentIndex]; } TrustedDomainInfo ++; } } } // // Indicate that the LSA policy should be handled. // InWorkstationInfo = InBuffer->WorkstationInfo; OutLsaPolicy = &NetlogonDomainInfo->LsaPolicy; break; case NETLOGON_QUERY_LSA_POLICY_INFO: // // Allocate the buffer to return. // NetlogonLsaPolicyInfo = MIDL_user_allocate( sizeof(*NetlogonLsaPolicyInfo) ); if ( NetlogonLsaPolicyInfo == NULL ) { Status = STATUS_NO_MEMORY; goto Cleanup; } // // Indicate that the LSA policy should be handled. // InWorkstationInfo = InBuffer->WorkstationInfo; OutLsaPolicy = NetlogonLsaPolicyInfo; break; default: Status = STATUS_INVALID_LEVEL; goto Cleanup; } // // If we're passing LSA policy back and forth between workstation/DC, // handle the next leg. // if ( InWorkstationInfo != NULL ) { OSVERSIONINFOEXW OsVersionInfoEx; POSVERSIONINFOEXW OsVersionInfoExPtr = NULL; LPWSTR OsName; LPWSTR AllocatedOsName = NULL; // // See if the caller passed the OS version to us. // if ( InWorkstationInfo->OsVersion.Length >= sizeof(OsVersionInfoEx) ) { // // Copy the version to get the alignment right // (since RPC thinks this is a WCHAR buffer). // RtlCopyMemory( &OsVersionInfoEx, InWorkstationInfo->OsVersion.Buffer, sizeof(OsVersionInfoEx) ); OsVersionInfoExPtr = &OsVersionInfoEx; NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrLogonGetDomainInfo: %ws is running NT %ld.%ld build %ld (%ld)\n", ComputerName, OsVersionInfoEx.dwMajorVersion, OsVersionInfoEx.dwMinorVersion, OsVersionInfoEx.dwBuildNumber, OsVersionInfoEx.wProductType )); } // // See if the caller passed us an OsName. // if ( InWorkstationInfo->OsName.Length ) { AllocatedOsName = LocalAlloc( 0, InWorkstationInfo->OsName.Length + sizeof(WCHAR)); if ( AllocatedOsName == NULL) { OsName = L"Windows 2000"; } else { RtlCopyMemory( AllocatedOsName, InWorkstationInfo->OsName.Buffer, InWorkstationInfo->OsName.Length ); AllocatedOsName[InWorkstationInfo->OsName.Length/sizeof(WCHAR)] = L'\0'; OsName = AllocatedOsName; } // // If the caller didn't pass us its OsName, // make one up. // (Only pre RTM versions of WIN 2000 did this.) // } else { if ( OsVersionInfoExPtr == NULL ) { OsName = L"Windows 2000"; } else { if ( OsVersionInfoExPtr->wProductType == VER_NT_WORKSTATION ) { OsName = L"Windows 2000 Professional"; } else { OsName = L"Windows 2000 Server"; } } } // // Set the DnsHostName on the computer object. // If the client handles SPN setting, get the DnsHostName from the DS // rather than setting it. // Status = LsaISetClientDnsHostName( ComputerName, ClientHandlesSpn ? NULL : InWorkstationInfo->DnsHostName, OsVersionInfoExPtr, OsName, ClientHandlesSpn ? &PreviousDnsHostName : NULL ); if ( !NT_SUCCESS(Status) ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonGetDomainInfo: Cannot set client DNS host name %lx (ignoring)\n", Status )); PreviousDnsHostName = NULL; // This isn't fatal } NlPrintDom((NL_MISC, DomainInfo, "NetrLogonGetDomainInfo: DnsHostName of %ws is %ws\n", ComputerName, PreviousDnsHostName )); if ( AllocatedOsName != NULL) { LocalFree( AllocatedOsName ); } // // Set the HOST/name SPN on the object as well. This // is handled mostly by the DS side of things. // if ( !ClientHandlesSpn ) { NlSetDsSPN( FALSE, // Don't wait for this to complete TRUE, // Set the SPN FALSE, // We've already set the Dns host name DomainInfo, DomainInfo->DomUncUnicodeComputerName, ComputerName, InWorkstationInfo->DnsHostName ); } // // Return the Previous DNS Host Name to the client // if ( NetlogonDomainInfo != NULL ) { RtlInitUnicodeString( &NetlogonDomainInfo->DnsHostNameInDs, PreviousDnsHostName ); PreviousDnsHostName = NULL; } // // Tell the caller there is no policy to return // OutLsaPolicy->LsaPolicySize = 0; OutLsaPolicy->LsaPolicy = NULL; } Status = STATUS_SUCCESS; // // Common exit point // Cleanup: // // If the request failed, be careful to not leak authentication // information. // if ( Status == STATUS_ACCESS_DENIED ) { RtlZeroMemory( ReturnAuthenticator, sizeof(*ReturnAuthenticator) ); } NlPrintDom((NL_SESSION_SETUP, DomainInfo, "NetrLogonGetDomainInfo: %ws %ld Returns 0x%lX\n", ComputerName, QueryLevel, Status )); if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } if ( IndexInReturnedList != NULL ) { LocalFree( IndexInReturnedList ); } if ( PreviousDnsHostName != NULL ) { MIDL_user_free( PreviousDnsHostName ); } if ( NT_SUCCESS(Status)) { if ( NetlogonDomainInfo != NULL ) { OutBuffer->DomainInfo = NetlogonDomainInfo; } else if ( NetlogonLsaPolicyInfo != NULL ) { OutBuffer->LsaPolicyInfo = NetlogonLsaPolicyInfo; } } else { if ( NetlogonDomainInfo != NULL ) { NlFreeOneDomainInfo( &NetlogonDomainInfo->PrimaryDomain ); for ( i=0; iTrustedDomainCount; i++ ) { NlFreeOneDomainInfo( &NetlogonDomainInfo->TrustedDomains[i] ); } if ( NetlogonDomainInfo->LsaPolicy.LsaPolicy != NULL ) { MIDL_user_free( NetlogonDomainInfo->LsaPolicy.LsaPolicy ); } MIDL_user_free( NetlogonDomainInfo ); } if ( NetlogonLsaPolicyInfo != NULL ) { if ( NetlogonLsaPolicyInfo->LsaPolicy != NULL ) { MIDL_user_free( NetlogonLsaPolicyInfo->LsaPolicy ); } MIDL_user_free( NetlogonLsaPolicyInfo ); } } if ( DomainLocked ) { UNLOCK_TRUST_LIST( DomainInfo ); LeaveCriticalSection( &NlGlobalDomainCritSect ); } return Status; } NTSTATUS NetrLogonSetServiceBits( IN LPWSTR ServerName, IN DWORD ServiceBitsOfInterest, IN DWORD ServiceBits ) /*++ Routine Description: Inidcates whether this DC is currently running the specified service. For instance, NetLogonSetServiceBits( DS_KDC_FLAG, DS_KDC_FLAG ); tells Netlogon the KDC is running. And NetLogonSetServiceBits( DS_KDC_FLAG, 0 ); tells Netlogon the KDC is not running. This out of proc API can set only a certain set of bits: DS_TIMESERV_FLAG DS_GOOD_TIMESERV_FLAG If other bits are attempted to be set, access denied is returned. Arguments: ServerName -- Name of the DC to retrieve the data from. ServiceBitsOfInterest - A mask of the service bits being changed, set, or reset by this call. Only the following flags are valid: DS_KDC_FLAG DS_DS_FLAG DS_TIMESERV_FLAG DS_GOOD_TIMESERV_FLAG ServiceBits - A mask indicating what the bits specified by ServiceBitsOfInterest should be set to. Return Value: STATUS_SUCCESS - Success. STATUS_ACCESS_DENIED - Caller does not have permission to call this API. STATUS_INVALID_PARAMETER - The parameters have extaneous bits set. --*/ { NET_API_STATUS NetStatus; // // Out of proc callers can set only certain bits // if ( (ServiceBitsOfInterest & ~DS_OUTOFPROC_VALID_SERVICE_BITS) != 0 ) { return STATUS_ACCESS_DENIED; } // // Perform access validation on the caller. // NetStatus = NetpAccessCheck( NlGlobalNetlogonSecurityDescriptor, // Security descriptor NETLOGON_SERVICE_ACCESS, // Desired access &NlGlobalNetlogonInfoMapping ); // Generic mapping if ( NetStatus != NERR_Success) { return STATUS_ACCESS_DENIED; } return I_NetLogonSetServiceBits( ServiceBitsOfInterest, ServiceBits ); UNREFERENCED_PARAMETER( ServerName ); } NET_API_STATUS NlComputeMd5Digest( IN LPBYTE Message, IN ULONG MessageSize, IN PNT_OWF_PASSWORD OwfPassword, OUT CHAR MessageDigest[NL_DIGEST_SIZE] ) /*++ Routine Description: Compute the message digest for Message. Arguments: Message - The message to compute the digest for. MessageSize - The size of Message in bytes. OwfPassword - Password of the account to used salt the digest MessageDigest - Returns the 128-bit digest of the message. Return Value: NERR_Success: the operation was successful ERROR_NOT_SUPPORTED: MD5 is not supported on this machine. --*/ { NET_API_STATUS NetStatus; NTSTATUS Status; PCHECKSUM_FUNCTION Check; PCHECKSUM_BUFFER CheckBuffer = NULL; BOOL Initialized = FALSE; // // Locate the checksum routine for the context, loading it if necessary from the // the crypto support DLL // Status = CDLocateCheckSum(KERB_CHECKSUM_MD5, &Check); if (!NT_SUCCESS(Status)) { NlPrint((NL_CRITICAL, "NlComputeMd5Digest: MD5 is not supported\n", DomainName )); NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } // // Initialize // Status = Check->Initialize(0, &CheckBuffer); if (!NT_SUCCESS(Status)) { NlPrint((NL_CRITICAL, "NlComputeMd5Digest: cannot initialize MD5 0x%lx\n", Status )); NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } Initialized = TRUE; // // First compute the digest of the OWF // Status = Check->Sum( CheckBuffer, sizeof(*OwfPassword), (PUCHAR) OwfPassword ); if (!NT_SUCCESS(Status)) { NlPrint((NL_CRITICAL, "NlComputeMd5Digest: cannot checksum OWF password 0x%lx\n", Status )); NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } // // Then compute the digest of the message itself // Status = Check->Sum( CheckBuffer, MessageSize, Message ); if (!NT_SUCCESS(Status)) { NlPrint((NL_CRITICAL, "NlComputeMd5Digest: cannot checksum message 0x%lx\n", Status )); NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } // // Grab the digest. // if ( Check->CheckSumSize != NL_DIGEST_SIZE ) { NlPrint((NL_CRITICAL, "NlComputeMd5Digest: digest is the wrong size.\n" )); NetStatus = ERROR_INTERNAL_ERROR; goto Cleanup; } Status = Check->Finalize(CheckBuffer, MessageDigest); if (!NT_SUCCESS(Status)) { NlPrint((NL_CRITICAL, "NlComputeMd5Digest: cannot checksum message 0x%lx\n", Status )); NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } // // Done. // NetStatus = NO_ERROR; Cleanup: if ( Initialized ) { Status = Check->Finish(&CheckBuffer); if (!NT_SUCCESS(Status)) { NlPrint((NL_CRITICAL, "NlComputeMd5Digest: cannot finish 0x%lx\n", Status )); } } return NetStatus; } NET_API_STATUS NetrLogonGetTrustRid( IN LPWSTR ServerName OPTIONAL, IN LPWSTR DomainName OPTIONAL, OUT PULONG Rid ) /*++ Routine Description: Returns the Rid of the account that ServerName uses in its secure channel to DomainName. Only an Admin or LocalSystem or LocalService may call this function. Arguments: ServerName - The name of the remote server. DomainName - The name (DNS or Netbios) of the domain the trust is to. NULL implies the domain the machine is a member of. Rid - Rid is the RID of the account in the specified domain that represents the trust relationship between the ServerName and DomainName. Return Value: NERR_Success: the operation was successful ERROR_NO_SUCH_DOMAIN: The specified domain does not exist. ERROR_NO_LOGON_SERVERS: There are currently no logon server available for the domain or there is some problem with the secure channel. ERROR_NOT_SUPPORTED: The specified trusted domain does not support digesting. --*/ { NET_API_STATUS NetStatus = NO_ERROR; NTSTATUS Status = STATUS_SUCCESS; PCLIENT_SESSION ClientSession = NULL; PDOMAIN_INFO DomainInfo = NULL; UNICODE_STRING DomainNameString; BOOL AmWriter = FALSE; ULONG LocalRid = 0; // // Perform access validation on the caller. // NetStatus = NetpAccessCheck( NlGlobalNetlogonSecurityDescriptor, // Security descriptor NETLOGON_SERVICE_ACCESS, // Desired access &NlGlobalNetlogonInfoMapping ); // Generic mapping if ( NetStatus != NERR_Success) { NetStatus = ERROR_ACCESS_DENIED; goto Cleanup; } // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( ServerName ); if ( DomainInfo == NULL ) { NetStatus = ERROR_INVALID_COMPUTERNAME; goto Cleanup; } // // On the PDC or BDC, // find the Client session for the domain. // On workstations, // find the primary domain client session. // if ( DomainName == NULL ) { DomainName = DomainInfo->DomUnicodeDomainName; } RtlInitUnicodeString( &DomainNameString, DomainName ); ClientSession = NlFindNamedClientSession( DomainInfo, &DomainNameString, NL_DIRECT_TRUST_REQUIRED | NL_ROLE_PRIMARY_OK, NULL ); if ( ClientSession == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonGetTrustRid: %ws: No such trusted domain\n", DomainName )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Become a writer of the ClientSession. // if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrintCs((NL_CRITICAL, ClientSession, "NetrLogonGetTrustRid: Can't become writer of client session.\n" )); NetStatus = ERROR_NO_LOGON_SERVERS; goto Cleanup; } AmWriter = TRUE; // // If this is a server secure channel (i.e. we are a DC and // this is our domain) we can get the RID from local SAM // if ( ClientSession->CsSecureChannelType == ServerSecureChannel ) { ULONG AccountRid = 0; Status = NlSamOpenNamedUser( DomainInfo, ClientSession->CsAccountName, NULL, &AccountRid, NULL ); // // Just stash it into the client session. // // Note that if we are a BDC, we also set RID during // secure channel setup to our PDC, so whoever writes // last is the winner. But hopefully the same value // will be written in both cases. // if ( NT_SUCCESS(Status) ) { ClientSession->CsAccountRid = AccountRid; } else { NlPrintDom(( NL_CRITICAL, DomainInfo, "NlUpdateRole: NlSamOpenNamedUser failed 0x%lx\n", Status )); } // // For all other secure channel types, we have to // get the RID as a side effect of setting up the // secure channel. Note that we merely refresh RID // here as we may already have it from a previous // successful secure channel setup. // } else if ( ClientSession->CsState != CS_AUTHENTICATED && NlTimeToReauthenticate(ClientSession) ) { // // Try to set up the channel. If we can't // don't error out, rather use RID that // we got when on one of the previous attemps. // Status = NlSessionSetup( ClientSession ); } // // Ensure that we return non-zero RID on success // LocalRid = ClientSession->CsAccountRid; if ( LocalRid != 0 ) { *Rid = LocalRid; NetStatus = NO_ERROR; } else { // // If the trust is not NT5, this call is not supported // if ( (ClientSession->CsFlags & CS_NT5_DOMAIN_TRUST) == 0 ) { NetStatus = ERROR_NOT_SUPPORTED; } else { NetStatus = ERROR_TRUSTED_RELATIONSHIP_FAILURE; } } // // Free any locally used resources. // Cleanup: if ( AmWriter ) { NlResetWriterClientSession( ClientSession ); } if ( ClientSession != NULL ) { NlUnrefClientSession( ClientSession ); } if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } return NetStatus; } NET_API_STATUS NetrLogonComputeServerDigest( IN LPWSTR ServerName OPTIONAL, IN ULONG Rid, IN LPBYTE Message, IN ULONG MessageSize, OUT CHAR NewMessageDigest[NL_DIGEST_SIZE], OUT CHAR OldMessageDigest[NL_DIGEST_SIZE] ) /*++ Routine Description: Compute the message digest for Message on the server. A digest is computed given the message and the password used on the account identified by teh account RID. Since there may be up to 2 passwords on the account (for interdomain trust), this routine returns 2 digets corresponding to the 2 passwords. If the account has just one password on the server side (true for any account other than the intedomain trust account) or the two passwords are the same the 2 digests returned will be identical. Only an Admin or LocalSystem or LocalService may call this function. Arguments: ServerName - The name of the remote server. Rid - The RID of the account to create the digest for. The RID must be the RID of a machine account or the API returns an error. Message - The message to compute the digest for. MessageSize - The size of Message in bytes. NewMessageDigest - Returns the 128-bit digest of the message corresponding to the new account password. OldMessageDigest - Returns the 128-bit digest of the message corresponding to the old account password. Return Value: NERR_Success: the operation was successful ERROR_NOT_SUPPORTED: The specified trusted domain does not support digesting. --*/ { NTSTATUS Status; NET_API_STATUS NetStatus; PDOMAIN_INFO DomainInfo = NULL; PSID UserSid = NULL; UNICODE_STRING UserSidString; PSAMPR_USER_INFO_BUFFER UserAllInfo = NULL; SID_AND_ATTRIBUTES_LIST ReverseMembership; LPWSTR LocalUserName = NULL; NT_OWF_PASSWORD NewOwfPassword; NT_OWF_PASSWORD OldOwfPassword; ULONG AccountRid; ULONG LocalUserAccountControl; // // Perform access validation on the caller. // NetStatus = NetpAccessCheck( NlGlobalNetlogonSecurityDescriptor, // Security descriptor NETLOGON_SERVICE_ACCESS, // Desired access &NlGlobalNetlogonInfoMapping ); // Generic mapping if ( NetStatus != NERR_Success) { NlPrint((NL_CRITICAL, "NetrLogonComputeServerDigest: Account %ld failed access check.\n", Rid )); NetStatus = ERROR_ACCESS_DENIED; goto Cleanup; } // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( ServerName ); if ( DomainInfo == NULL ) { NlPrint((NL_CRITICAL, "NetrLogonComputeServerDigest: Account %ld: cannot find domain for %ws\n", Rid, ServerName )); NetStatus = ERROR_INVALID_COMPUTERNAME; goto Cleanup; } // // Convert the account RID to an account SID // NetStatus = NetpDomainIdToSid( DomainInfo->DomAccountDomainId, Rid, &UserSid ); if ( NetStatus != NO_ERROR ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonComputeServerDigest: Account %ld: cannot convert domain ID to sid.: %ld\n", Rid, NetStatus )); goto Cleanup; } // // Get the info about the user. // // Use SamIGetUserLogonInformation instead of SamrOpenUser. // The former is more efficient (since it only does one // DirSearch and doesn't lock the global SAM lock) and more powerful // (since it returns UserAllInformation). // UserSidString.Buffer = UserSid; UserSidString.MaximumLength = UserSidString.Length = (USHORT) RtlLengthSid( UserSid ); Status = SamIGetUserLogonInformation( DomainInfo->DomSamAccountDomainHandle, SAM_NO_MEMBERSHIPS | // Don't need group memberships SAM_OPEN_BY_SID, // Next parameter is the SID of the account &UserSidString, &UserAllInfo, &ReverseMembership, NULL ); if ( !NT_SUCCESS(Status) ) { NlPrintDom(( NL_CRITICAL, DomainInfo, "NetrLogonComputeServerDigest: Account %ld: Cannot SamIGetUserLogonInfo 0x%lx\n", Rid, Status )); if ( Status == STATUS_NOT_FOUND || Status == STATUS_OBJECT_NAME_NOT_FOUND ) { NetStatus = ERROR_NO_SUCH_USER; } else { NetStatus = NetpNtStatusToApiStatus(Status); } goto Cleanup; } NlPrint((NL_ENCRYPT, "NetrLogonComputeServerDigest: %ld: %wZ: Message: ", Rid, &UserAllInfo->All.UserName )); NlpDumpBuffer(NL_ENCRYPT, Message, MessageSize ); // // Ensure the account is a machine account. // if ( (UserAllInfo->All.UserAccountControl & USER_MACHINE_ACCOUNT_MASK) == 0 ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonComputeServerDigest: Account %ld isn't a machine account\n", Rid )); NetStatus = ERROR_NO_SUCH_USER; goto Cleanup; } if ( UserAllInfo->All.UserAccountControl & USER_ACCOUNT_DISABLED ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonComputeServerDigest: Account %ld is disabled\n", Rid )); NetStatus = ERROR_NO_SUCH_USER; goto Cleanup; } // // Get the password(s) for the account. For interdomain trust // trust account, get both current and previous passwords // LocalUserName = LocalAlloc( 0, UserAllInfo->All.UserName.Length + sizeof(WCHAR) ); if ( LocalUserName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } RtlCopyMemory( LocalUserName, UserAllInfo->All.UserName.Buffer, UserAllInfo->All.UserName.Length ); LocalUserName[ (UserAllInfo->All.UserName.Length)/sizeof(WCHAR) ] = UNICODE_NULL; // // NlGetIncomingPassword checks for the exact equality of the user account control // to the trust account flags. Therefore pass only these flags if they are set in // the data retuned from SAM. // LocalUserAccountControl = UserAllInfo->All.UserAccountControl; if ( UserAllInfo->All.UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT ) { LocalUserAccountControl = USER_INTERDOMAIN_TRUST_ACCOUNT; } if ( UserAllInfo->All.UserAccountControl & USER_DNS_DOMAIN_TRUST_ACCOUNT ) { LocalUserAccountControl = USER_DNS_DOMAIN_TRUST_ACCOUNT; } Status = NlGetIncomingPassword( DomainInfo, LocalUserName, NullSecureChannel, // The account control bits are passed next LocalUserAccountControl, TRUE, // Fail for disabled accounts &NewOwfPassword, (UserAllInfo->All.UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT) ? &OldOwfPassword : // Get previous password for interdomain account NULL, &AccountRid, NULL, // Don't need trust attributes NULL ); // Don't need the account type if ( !NT_SUCCESS(Status) ) { NlPrintDom(( NL_CRITICAL, DomainInfo, "NetrLogonComputeServerDigest: Can't NlGetIncomingPassword for %wZ 0x%lx.\n", &UserAllInfo->All.UserName, Status )); NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } NlAssert( Rid == AccountRid ); // // If there is no old password on the account, // use the new one // if ( (UserAllInfo->All.UserAccountControl & USER_INTERDOMAIN_TRUST_ACCOUNT) == 0 ) { RtlCopyMemory( &OldOwfPassword, &NewOwfPassword, sizeof(OldOwfPassword) ); } // // Compute the new message digest. // NetStatus = NlComputeMd5Digest( Message, MessageSize, &NewOwfPassword, NewMessageDigest ); if ( NetStatus != NO_ERROR ) { NlPrint(( NL_CRITICAL, "NetrLogonComputeServerDigest: %ld: NlComputeMd5Digest failed (1): 0x%lx\n", Rid, NetStatus )); goto Cleanup; } NlPrint((NL_ENCRYPT, "NetrLogonComputeServerDigest: %ld: New Password: ", Rid )); NlpDumpBuffer(NL_ENCRYPT, &NewOwfPassword, sizeof(NewOwfPassword) ); NlPrint((NL_ENCRYPT, "NetrLogonComputeServerDigest: %ld: New Digest: ", Rid )); NlpDumpBuffer(NL_ENCRYPT, NewMessageDigest, sizeof(NewMessageDigest) ); // // Compute the old message digest. // NetStatus = NlComputeMd5Digest( Message, MessageSize, &OldOwfPassword, OldMessageDigest ); if ( NetStatus != NO_ERROR ) { NlPrint(( NL_CRITICAL, "NetrLogonComputeServerDigest: %ld: NlComputeMd5Digest failed (2): 0x%lx\n", Rid, NetStatus )); goto Cleanup; } NlPrint((NL_ENCRYPT, "NetrLogonComputeServerDigest: %ld: Old Password: ", Rid )); NlpDumpBuffer(NL_ENCRYPT, &OldOwfPassword, sizeof(OldOwfPassword) ); NlPrint((NL_ENCRYPT, "NetrLogonComputeServerDigest: %ld: Old Digest: ", Rid )); NlpDumpBuffer(NL_ENCRYPT, OldMessageDigest, sizeof(OldMessageDigest) ); // // Free any locally used resources. // Cleanup: if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } if ( UserSid != NULL ) { NetpMemoryFree( UserSid ); } if ( LocalUserName != NULL ) { LocalFree( LocalUserName ); } if ( UserAllInfo != NULL ) { SamIFree_SAMPR_USER_INFO_BUFFER( UserAllInfo, UserAllInformation ); } return NetStatus; } NET_API_STATUS NetrLogonComputeClientDigest( IN LPWSTR ServerName OPTIONAL, IN LPWSTR DomainName OPTIONAL, IN LPBYTE Message, IN ULONG MessageSize, OUT CHAR NewMessageDigest[NL_DIGEST_SIZE], OUT CHAR OldMessageDigest[NL_DIGEST_SIZE] ) /*++ Routine Description: Compute the message digest for Message on the client. A digest is computed given the message and the password used on the account identified by the domain name. Since there are two passwords on the account on the client side, this routine returns 2 digests corresponding to the 2 passwords. If the two passwords are the same the 2 digests returned will be identical. Only an Admin or LocalSystem or LocalService may call this function. Arguments: ServerName - The name of the remote server. DomainName - The name (DNS or Netbios) of the domain the trust is to. NULL implies the domain the machine is a member of. Message - The message to compute the digest for. MessageSize - The size of Message in bytes. NewMessageDigest - Returns the 128-bit digest of the message corresponding to the new password NewMessageDigest - Returns the 128-bit digest of the message corresponding to the new password Return Value: NERR_Success: the operation was successful ERROR_NOT_SUPPORTED: The specified trusted domain does not support digesting. --*/ { NET_API_STATUS NetStatus = NO_ERROR; NTSTATUS Status = STATUS_SUCCESS; PCLIENT_SESSION ClientSession = NULL; PDOMAIN_INFO DomainInfo = NULL; UNICODE_STRING DomainNameString; PUNICODE_STRING NewPassword = NULL; PUNICODE_STRING OldPassword = NULL; ULONG DummyPasswordVersionNumber; NT_OWF_PASSWORD NewOwfPassword; NT_OWF_PASSWORD OldOwfPassword; NlPrint((NL_ENCRYPT, "NetrLogonComputeClientDigest: %ws: Message: ", DomainName )); NlpDumpBuffer(NL_ENCRYPT, Message, MessageSize ); // // Perform access validation on the caller. // NetStatus = NetpAccessCheck( NlGlobalNetlogonSecurityDescriptor, // Security descriptor NETLOGON_SERVICE_ACCESS, // Desired access &NlGlobalNetlogonInfoMapping ); // Generic mapping if ( NetStatus != NERR_Success) { NetStatus = ERROR_ACCESS_DENIED; goto Cleanup; } // // Lookup which domain this call pertains to. // DomainInfo = NlFindDomainByServerName( ServerName ); if ( DomainInfo == NULL ) { NetStatus = ERROR_INVALID_COMPUTERNAME; goto Cleanup; } // // On the PDC or BDC, // find the Client session for the domain. // On workstations, // find the primary domain client session. // if ( DomainName == NULL ) { DomainName = DomainInfo->DomUnicodeDomainName; } RtlInitUnicodeString( &DomainNameString, DomainName ); ClientSession = NlFindNamedClientSession( DomainInfo, &DomainNameString, NL_DIRECT_TRUST_REQUIRED | NL_ROLE_PRIMARY_OK, NULL ); if ( ClientSession == NULL ) { NlPrintDom((NL_CRITICAL, DomainInfo, "NetrLogonComputeClientDigest: %ws: No such trusted domain\n", DomainName )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Get the two passwords on the account in clear // Status = NlGetOutgoingPassword( ClientSession, &NewPassword, &OldPassword, &DummyPasswordVersionNumber, NULL ); // No need to return password set time if ( !NT_SUCCESS(Status) ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NetrLogonComputeClientDigest: cannot NlGetOutgoingPassword 0x%lx\n", Status )); // // Return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } // // Compute the new OWF password // if ( NewPassword != NULL ) { Status = RtlCalculateNtOwfPassword( NewPassword, &NewOwfPassword ); if ( !NT_SUCCESS( Status ) ) { // // return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } // // If no new password exists on the account, // use a blank password // } else { UNICODE_STRING TempUnicodeString; RtlInitUnicodeString(&TempUnicodeString, NULL); Status = RtlCalculateNtOwfPassword( &TempUnicodeString, &NewOwfPassword ); if ( !NT_SUCCESS(Status) ) { NlPrint(( NL_CRITICAL, "NetrLogonComputeClientDigest: %ws Cannot RtlCalculateNtOwfPassword (NULL) 0x%lx\n", DomainName, Status )); NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } } // // Compute the old OWF password // if ( OldPassword != NULL ) { Status = RtlCalculateNtOwfPassword( OldPassword, &OldOwfPassword ); if ( !NT_SUCCESS( Status ) ) { // // return more appropriate error. // if ( !NlpIsNtStatusResourceError( Status )) { Status = STATUS_NO_TRUST_LSA_SECRET; } NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } // // If no old password exists on the account, // use the new password in place of the old one // } else { RtlCopyMemory( &OldOwfPassword, &NewOwfPassword, sizeof(OldOwfPassword) ); } // // Compute the new message digest. // NetStatus = NlComputeMd5Digest( Message, MessageSize, &NewOwfPassword, NewMessageDigest ); if ( NetStatus != NO_ERROR ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NetrLogonComputeClientDigest: cannot NlComputeMd5Digest (1) 0x%lx\n", NetStatus )); goto Cleanup; } NlPrint((NL_ENCRYPT, "NetrLogonComputeClientDigest: %ws: New Password: ", DomainName )); NlpDumpBuffer(NL_ENCRYPT, &NewOwfPassword, sizeof(NewOwfPassword) ); NlPrint((NL_ENCRYPT, "NetrLogonComputeClientDigest: %ws: New Digest: ", DomainName )); NlpDumpBuffer(NL_ENCRYPT, NewMessageDigest, sizeof(NewMessageDigest) ); // // Compute the old message digest. // NetStatus = NlComputeMd5Digest( Message, MessageSize, &OldOwfPassword, OldMessageDigest ); if ( NetStatus != NO_ERROR ) { NlPrintCs(( NL_CRITICAL, ClientSession, "NetrLogonComputeClientDigest: cannot NlComputeMd5Digest (2) 0x%lx\n", NetStatus )); goto Cleanup; } NlPrint((NL_ENCRYPT, "NetrLogonComputeClientDigest: %ws: Old Password: ", DomainName )); NlpDumpBuffer(NL_ENCRYPT, &OldOwfPassword, sizeof(OldOwfPassword) ); NlPrint((NL_ENCRYPT, "NetrLogonComputeClientDigest: %ws: Old Digest: ", DomainName )); NlpDumpBuffer(NL_ENCRYPT, OldMessageDigest, sizeof(OldMessageDigest) ); // // Free any locally used resources. // Cleanup: if ( ClientSession != NULL ) { NlUnrefClientSession( ClientSession ); } if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } if ( NewPassword != NULL ) { LocalFree( NewPassword ); } if ( OldPassword != NULL ) { LocalFree( OldPassword ); } return NetStatus; } NET_API_STATUS NetrLogonGetTimeServiceParentDomain( IN LPWSTR ServerName OPTIONAL, OUT LPWSTR *DomainName, OUT PBOOL PdcSameSite ) /*++ Routine Description: Returns the domain name of the domain that is logically the "parent" of this domain. The returned domain name is suitable for passing into the NetLogonGetTrustRid and NetLogonComputeClientDigest API. On a workstation or member server, the returned domain name is that of the domain that ServerName is a member of. On a DC that is at the root of the forest, ERROR_NO_SUCH_DOMAIN is returned. On a DC that is at the root of a tree in the forest, the name of a trusted domain that is also at the root of a tree in the forest is returned. On any other DC, the name of the domain that is directly the parent domain is returned. (See the notes on multiple hosted domains in the code below.) Only an Admin or LocalSystem may call this function. Arguments: ServerName - The name of the remote server. DomainName - Returns the name of the parent domain. The returned buffer should be freed using NetApiBufferFree PdcSameSite - Return TRUE if the PDC of ServerName's domain is in the same site as ServerName. (This value should be ignored if ServerName is not a DC.) Return Value: NERR_Success: the operation was successful ERROR_NO_SUCH_DOMAIN: This server is a DC in the domain that is at the root of the forest. --*/ { NET_API_STATUS NetStatus; NTSTATUS Status; PCLIENT_SESSION ClientSession = NULL; PDOMAIN_INFO DomainInfo = NULL; BOOLEAN IsSameSite; // // Perform access validation on the caller. // NetStatus = NetpAccessCheck( NlGlobalNetlogonSecurityDescriptor, // Security descriptor NETLOGON_SERVICE_ACCESS, // Desired access &NlGlobalNetlogonInfoMapping ); // Generic mapping if ( NetStatus != NERR_Success) { NetStatus = ERROR_ACCESS_DENIED; goto Cleanup; } // // Lookup which domain this call pertains to. // // MULTIHOST: This API doesn't take the hosted domain name on purpose. // When I do multiple hosted domains, this API should find the // DomainInfo structure for the hosted domain that's closest to the root. // We'll return the parent of that domain. // // Since there is only one physical clock on this machine, we'll only run // one copy of the time service. It should sync from as high up the tree // as we have trust to. // UNREFERENCED_PARAMETER( ServerName ); DomainInfo = NlFindDomainByServerName( NULL ); if ( DomainInfo == NULL ) { NetStatus = ERROR_INVALID_COMPUTERNAME; goto Cleanup; } // // On a workstation, // Use the session for the domain we're a member of. // if ( NlGlobalMemberWorkstation ) { ClientSession = NlRefDomClientSession( DomainInfo ); IsSameSite = TRUE; // // On a DC, // Use the session for the domain representing our parent domain // } else { // // Determine whether the PDC is in the same site // Status = SamISameSite( &IsSameSite ); if ( !NT_SUCCESS(Status) ) { NlPrintDom(( NL_CRITICAL, DomainInfo, "NetrLogonGetTimeServiceParentDomain: Cannot SamISameSite.\n" )); NetStatus = NetpNtStatusToApiStatus(Status); goto Cleanup; } ClientSession = NlRefDomParentClientSession( DomainInfo ); } if ( ClientSession == NULL ) { NlPrintDom(( NL_CRITICAL, DomainInfo, "NetrLogonGetTimeServiceParentDomain: Cannot find trust to my parent domain.\n" )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Return the name of the trusted parent domain to the caller. // LOCK_TRUST_LIST( DomainInfo ); if ( ClientSession->CsDnsDomainName.Length == 0 ) { *DomainName = NetpAllocWStrFromWStr( ClientSession->CsNetbiosDomainName.Buffer ); } else { *DomainName = NetpAllocWStrFromWStr( ClientSession->CsDnsDomainName.Buffer ); } UNLOCK_TRUST_LIST( DomainInfo ); if ( *DomainName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } *PdcSameSite = IsSameSite; NlPrintDom(( NL_SESSION_SETUP, DomainInfo, "NetrLogonGetTimeServiceParentDomain: %ws is the parent domain. (PdcSameSite: %ld)\n", *DomainName, IsSameSite )); // // Free any locally used resources. // Cleanup: if ( ClientSession != NULL ) { NlUnrefClientSession( ClientSession ); } if ( DomainInfo != NULL ) { NlDereferenceDomain( DomainInfo ); } return NetStatus; } DWORD NlSetDsSPNWorker( PNL_SPN_UPDATE Update ) /*++ Routine Description: Updates the SPN of the computer object described in the NL_SPN_UPDATE structure. The SPN is updated, but the rules about SPN update are left to the DS. Arguments: Update - Update record describing the name of the computer object and the SPN to use. Return Value: ignored - this is a thread pool worker function. --*/ { NET_API_STATUS NetStatus = NO_ERROR; ULONG CrackStatus = DS_NAME_NO_ERROR; LPWSTR DnsHostNameValues[2]; LPWSTR SpnArray[3]; LPWSTR DnsSpn = NULL; LPWSTR NetbiosSpn = NULL; LDAPModW DnsHostNameAttr; LDAPModW SpnAttr; LDAPModW *Mods[3] = {NULL}; HANDLE hDs = NULL; LDAP *LdapHandle = NULL; LDAPMessage *LdapMessage = NULL; PDS_NAME_RESULTW CrackedName = NULL; LPWSTR DnOfAccount = NULL; LPWSTR NameToCrack; DWORD SamNameSize; WCHAR SamName[ DNLEN + 1 + CNLEN + 1 + 1]; ULONG LdapStatus; LONG LdapOption; LDAP_TIMEVAL LdapTimeout; ULONG MessageNumber; // // Ldap modify control needed to indicate that the // existing values of the modified attributes should // be left intact and the missing ones should be added. // Without this control, a modification of an attribute // that results in an addition of a value that already // exists will fail. // LDAPControl ModifyControl = { LDAP_SERVER_PERMISSIVE_MODIFY_OID_W, { 0, NULL }, FALSE }; PLDAPControl ModifyControlArray[2] = { &ModifyControl, NULL }; // // Sanity check computer name // if ( wcslen( Update->NetbiosComputerName ) > CNLEN ) { NetStatus = ERROR_INVALID_COMPUTERNAME; goto Cleanup; } // // Prepare DnsHostName modification entry // if ( Update->SetDnsHostName ) { DnsHostNameValues[0] = Update->DnsHostName; DnsHostNameValues[1] = NULL; NlPrint(( NL_MISC, "SPN: Setting DnsHostName %ws\n", DnsHostNameValues[0] )); // // If we set both DnsHostName and SPN, then DnsHostName is // missing, so add it. If we set DnsHostName only, then // DnsHostName already exists (but incorrect), so replace it. // if ( Update->SetSpn ) { DnsHostNameAttr.mod_op = LDAP_MOD_ADD; } else { DnsHostNameAttr.mod_op = LDAP_MOD_REPLACE; } DnsHostNameAttr.mod_type = L"DnsHostName"; DnsHostNameAttr.mod_values = DnsHostNameValues; Mods[0] = &DnsHostNameAttr; Mods[1] = NULL; } // // Prepare SPN modification entries // if ( Update->SetSpn ) { LPBYTE Where; DWORD SpnSize; // // Build the DNS SPN // SpnSize = (wcslen( Update->DnsHostName ) + 1) * sizeof( WCHAR ); SpnSize += sizeof( NL_HOST_PREFIX ) ; DnsSpn = (LPWSTR) LocalAlloc( 0, SpnSize ); if ( DnsSpn == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } wcscpy( DnsSpn, NL_HOST_PREFIX ); wcscpy( DnsSpn + (sizeof( NL_HOST_PREFIX ) / sizeof(WCHAR) ) - 1, Update->DnsHostName ); // // Build the Netbios SPN // SpnSize = (wcslen( Update->NetbiosComputerName ) + 1) * sizeof( WCHAR ); SpnSize += sizeof( NL_HOST_PREFIX ) ; NetbiosSpn = (LPWSTR) LocalAlloc( 0, SpnSize ); if ( NetbiosSpn == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } wcscpy( NetbiosSpn, NL_HOST_PREFIX ); wcscpy( NetbiosSpn + (sizeof( NL_HOST_PREFIX ) / sizeof(WCHAR) ) - 1, Update->NetbiosComputerName ); NlPrint(( NL_MISC, "SPN: Setting SPN %ws and %ws\n", DnsSpn, NetbiosSpn )); SpnArray[0] = DnsSpn; SpnArray[1] = NetbiosSpn; SpnArray[2] = NULL; SpnAttr.mod_op = LDAP_MOD_ADD; SpnAttr.mod_type = L"ServicePrincipalName"; SpnAttr.mod_values = SpnArray; // // Use the first modification entry slot available (actually, // when we set SPNs, we always set DnsHostName first, but // let's be general and check it here). // if ( Mods[0] == NULL ) { Mods[0] = &SpnAttr; Mods[1] = NULL; } else { Mods[1] = &SpnAttr; Mods[2] = NULL; } } // // The name of the computer object is // \$ // wcscpy( SamName, Update->NetbiosDomainName ); wcscat( SamName, L"\\" ); wcscat( SamName, Update->NetbiosComputerName ); wcscat( SamName, L"$" ); // // Bind to the DS on the DC. // NetStatus = DsBindW( Update->UncDcName, NULL, &hDs ); if ( NetStatus != NO_ERROR ) { NlPrint(( NL_CRITICAL, "SPN: Cannot bind to DS on %ws: %ld\n", Update->UncDcName, NetStatus )); goto Cleanup ; } // // Crack the sam account name into a DN: // NameToCrack = SamName; NetStatus = DsCrackNamesW( hDs, 0, DS_NT4_ACCOUNT_NAME, DS_FQDN_1779_NAME, 1, &NameToCrack, &CrackedName ); if ( NetStatus != NO_ERROR ) { NlPrint(( NL_CRITICAL, "SPN: CrackNames failed on %ws for %ws: %ld\n", Update->UncDcName, SamName, NetStatus )); goto Cleanup ; } if ( CrackedName->cItems != 1 ) { CrackStatus = DS_NAME_ERROR_NOT_UNIQUE; NlPrint(( NL_CRITICAL, "SPN: Cracked Name is not unique on %ws for %ws: %ld\n", Update->UncDcName, SamName, NetStatus )); goto Cleanup ; } if ( CrackedName->rItems[ 0 ].status != DS_NAME_NO_ERROR ) { NlPrint(( NL_CRITICAL, "SPN: CrackNames failed on %ws for %ws: substatus %ld\n", Update->UncDcName, SamName, CrackedName->rItems[ 0 ].status )); CrackStatus = CrackedName->rItems[ 0 ].status; goto Cleanup ; } DnOfAccount = CrackedName->rItems[0].pName; // // Open an LDAP connection to the DC and set useful options // LdapHandle = ldap_init( Update->UncDcName+2, LDAP_PORT ); if ( LdapHandle == NULL ) { NetStatus = GetLastError(); NlPrint(( NL_CRITICAL, "SPN: ldap_init failed on %ws for %ws: %ld\n", Update->UncDcName, SamName, NetStatus )); goto Cleanup; } // 30 second timeout LdapOption = 30; LdapStatus = ldap_set_optionW( LdapHandle, LDAP_OPT_TIMELIMIT, &LdapOption ); if ( LdapStatus != LDAP_SUCCESS ) { NlPrint(( NL_CRITICAL, "SPN: ldap_set_option LDAP_OPT_TIMELIMIT failed on %ws for %ws: %ld: %s\n", Update->UncDcName, SamName, LdapStatus, ldap_err2stringA( LdapStatus ))); NetStatus = LdapMapErrorToWin32(LdapStatus); goto Cleanup; } // Don't chase referals LdapOption = PtrToLong(LDAP_OPT_OFF); LdapStatus = ldap_set_optionW( LdapHandle, LDAP_OPT_REFERRALS, &LdapOption ); if ( LdapStatus != LDAP_SUCCESS ) { NlPrint(( NL_CRITICAL, "SPN: ldap_set_option LDAP_OPT_REFERRALS failed on %ws for %ws: %ld: %s\n", Update->UncDcName, SamName, LdapStatus, ldap_err2stringA( LdapStatus ))); NetStatus = LdapMapErrorToWin32(LdapStatus); goto Cleanup; } // Set the option telling LDAP that I passed it an explicit DC name and // that it can avoid the DsGetDcName. LdapOption = PtrToLong(LDAP_OPT_ON); LdapStatus = ldap_set_optionW( LdapHandle, LDAP_OPT_AREC_EXCLUSIVE, &LdapOption ); if ( LdapStatus != LDAP_SUCCESS ) { NlPrint(( NL_CRITICAL, "SPN: ldap_set_option LDAP_OPT_AREC_EXCLUSIVE failed on %ws for %ws: %ld: %s\n", Update->UncDcName, SamName, LdapStatus, ldap_err2stringA( LdapStatus ))); NetStatus = LdapMapErrorToWin32(LdapStatus); goto Cleanup; } // // Bind to the DC // LdapStatus = ldap_bind_s( LdapHandle, NULL, // No DN of account to authenticate as NULL, // Default credentials LDAP_AUTH_NEGOTIATE ); if ( LdapStatus != LDAP_SUCCESS ) { NlPrint(( NL_CRITICAL, "SPN: Cannot ldap_bind to %ws for %ws: %ld: %s\n", Update->UncDcName, SamName, LdapStatus, ldap_err2stringA( LdapStatus ))); NetStatus = LdapMapErrorToWin32(LdapStatus); goto Cleanup; } // // Write the modifications // LdapStatus = ldap_modify_extW( LdapHandle, DnOfAccount, Mods, (PLDAPControl *) &ModifyControlArray, NULL, // No client controls &MessageNumber ); if ( LdapStatus != LDAP_SUCCESS ) { NlPrint(( NL_CRITICAL, "SPN: Cannot ldap_modify on %ws for %ws: %ld: %s\n", Update->UncDcName, DnOfAccount, LdapStatus, ldap_err2stringA( LdapStatus ))); NetStatus = LdapMapErrorToWin32(LdapStatus); goto Cleanup; } // Wait for the modify to complete LdapTimeout.tv_sec = NlGlobalParameters.ShortApiCallPeriod / 1000, // Don't wait forever LdapTimeout.tv_usec = 0; LdapStatus = ldap_result( LdapHandle, MessageNumber, LDAP_MSG_ALL, &LdapTimeout, &LdapMessage ); switch ( LdapStatus ) { case -1: NlPrint(( NL_CRITICAL, "SPN: Cannot ldap_result on %ws for %ws: %ld: %s\n", Update->UncDcName, SamName, LdapHandle->ld_errno, ldap_err2stringA( LdapHandle->ld_errno ))); NetStatus = LdapMapErrorToWin32(LdapStatus); goto Cleanup; case 0: NlPrint(( NL_CRITICAL, "SPN: ldap_result timeout on %ws for %ws.\n", Update->UncDcName, SamName )); NetStatus = LdapMapErrorToWin32(LdapStatus); goto Cleanup; case LDAP_RES_MODIFY: if ( LdapMessage->lm_returncode != 0 ) { NlPrint(( NL_CRITICAL, "SPN: Cannot ldap_result on %ws for %ws: %ld: %s\n", Update->UncDcName, SamName, LdapMessage->lm_returncode, ldap_err2stringA( LdapMessage->lm_returncode ))); NetStatus = LdapMapErrorToWin32(LdapMessage->lm_returncode); goto Cleanup; } NlPrint(( NL_MISC, "SPN: Set successfully on DC %ws\n", Update->UncDcName )); break; // This is what we expect default: NlPrint(( NL_CRITICAL, "SPN: ldap_result unexpected result on %ws for %ws: %ld\n", Update->UncDcName, SamName, LdapStatus )); NetStatus = LdapMapErrorToWin32(LdapStatus); goto Cleanup; } Cleanup: // // Log the failure in the event log, if requested. // Try to output the most specific error. // if ( CrackStatus != DS_NAME_NO_ERROR && Update->WriteEventLogOnFailure ) { // // Try to log a more descriptive error message // if ( CrackStatus == DS_NAME_ERROR_NOT_UNIQUE ) { LPWSTR MsgStrings[2]; MsgStrings[0] = Update->UncDcName; MsgStrings[1] = SamName; NlpWriteEventlog( NELOG_NetlogonSpnMultipleSamAccountNames, EVENTLOG_ERROR_TYPE, NULL, 0, MsgStrings, 2 ); // // Log a generic crack name error message // } else { LPWSTR MsgStrings[4]; // Each byte of the status code will transform into one character 0-F WCHAR NetStatusString[sizeof(WCHAR) * (sizeof(NetStatus) + 1)]; WCHAR CrackStatusString[sizeof(WCHAR) * (sizeof(CrackStatus) + 1)]; swprintf( NetStatusString, L"%lx", NetStatus ); swprintf( CrackStatusString, L"%lx", CrackStatus ); MsgStrings[0] = Update->UncDcName; MsgStrings[1] = SamName; MsgStrings[2] = NetStatusString; MsgStrings[3] = CrackStatusString; NlpWriteEventlog( NELOG_NetlogonSpnCrackNamesFailure, EVENTLOG_ERROR_TYPE, NULL, 0, MsgStrings, 4 ); } // // Log the more generic error // } else if ( NetStatus != NO_ERROR && Update->WriteEventLogOnFailure ) { if ( Update->SetDnsHostName ) { LPWSTR MsgStrings[2]; if ( Update->DnsHostName != NULL ) { MsgStrings[0] = Update->DnsHostName; } else { MsgStrings[0] = L""; } MsgStrings[1] = (LPWSTR) ULongToPtr( NetStatus ); NlpWriteEventlog( NELOG_NetlogonFailedDnsHostNameUpdate, EVENTLOG_ERROR_TYPE, (LPBYTE)&NetStatus, sizeof(NetStatus), MsgStrings, 2 | NETP_LAST_MESSAGE_IS_NETSTATUS ); } if ( Update->SetSpn ) { LPWSTR MsgStrings[3]; if ( DnsSpn != NULL ) { MsgStrings[0] = DnsSpn; } else { MsgStrings[0] = L""; } if ( NetbiosSpn != NULL ) { MsgStrings[1] = NetbiosSpn; } else { MsgStrings[1] = L""; } MsgStrings[2] = (LPWSTR) ULongToPtr( NetStatus ); NlpWriteEventlog( NELOG_NetlogonFailedSpnUpdate, EVENTLOG_ERROR_TYPE, (LPBYTE)&NetStatus, sizeof(NetStatus), MsgStrings, 3 | NETP_LAST_MESSAGE_IS_NETSTATUS ); } } if ( hDs ) { DsUnBind( &hDs ); } if ( CrackedName ) { DsFreeNameResultW( CrackedName ); } if ( LdapMessage != NULL ) { ldap_msgfree( LdapMessage ); } if ( LdapHandle != NULL ) { ldap_unbind_s( LdapHandle ); } if ( DnsSpn ) { LocalFree( DnsSpn ); } if ( NetbiosSpn ) { LocalFree( NetbiosSpn ); } if ( Update ) { LocalFree( Update ); } return 0; } NET_API_STATUS NlSetDsSPN( IN BOOLEAN Synchronous, IN BOOLEAN SetSpn, IN BOOLEAN SetDnsHostName, IN PDOMAIN_INFO DomainInfo, IN LPWSTR UncDcName, IN LPWSTR ComputerName, IN LPWSTR DnsHostName ) /*++ Routine Description: Queues an update request to the thread pool for later execution in a worker thread. Arguments: Synchronous - TRUE if the operation is to complete before this procedure returns SetSpn - TRUE if the SPN is to be updated SetDnsHostName - TRUE if the Dns host name is to be updated DomainInfo - Hosted Domain this object is in UncDcName - UNC name of the DC to make this call on ComputerName - Name of the computer. This is (usually) equivalent to the netbios name, without the '$' on the end. DnsHostName - DNS Hostname of the computer. This is in FQDN format: longcomputername.dns.domain.com Return Value: ERROR_NOT_ENOUGH_MEMORY - No memory to queue the worker request NO_ERROR - Queued. --*/ { NET_API_STATUS NetStatus; PNL_SPN_UPDATE Update; DWORD Size; DWORD NetbiosComputerNameSize; DWORD DnsHostNameSize; DWORD DcNameSize; WCHAR NetbiosDomainName[DNLEN+1]; DWORD NetbiosDomainNameSize; LPBYTE Where; // // Silently ignore clients with no DNS host name // if ( DnsHostName == NULL ) { return NO_ERROR; } if ( !SetSpn && !SetDnsHostName ) { return NO_ERROR; } // // Sanity check computer name // if ( wcslen( ComputerName ) > CNLEN ) { return ERROR_INVALID_PARAMETER; } // // Grab the Netbios Domain Name // EnterCriticalSection( &NlGlobalDomainCritSect ); wcscpy( NetbiosDomainName, DomainInfo->DomUnicodeDomainName ); LeaveCriticalSection( &NlGlobalDomainCritSect ); // // Allocate a workitem // DnsHostNameSize = wcslen( DnsHostName ) * sizeof(WCHAR) + sizeof(WCHAR); NetbiosComputerNameSize = wcslen( ComputerName ) * sizeof(WCHAR) + sizeof(WCHAR); DcNameSize = wcslen( UncDcName ) * sizeof(WCHAR) + sizeof(WCHAR); NetbiosDomainNameSize = wcslen( NetbiosDomainName ) * sizeof(WCHAR) + sizeof(WCHAR); Size = sizeof( NL_SPN_UPDATE ) + DnsHostNameSize + NetbiosComputerNameSize + DcNameSize + NetbiosDomainNameSize + NL_MAX_DNS_LENGTH * sizeof(WCHAR) + sizeof(WCHAR); Update = LocalAlloc( 0, Size ); if ( Update == NULL ) { return ERROR_NOT_ENOUGH_MEMORY ; } // // Build the update request: // Update->SetSpn = SetSpn; Update->SetDnsHostName = SetDnsHostName; Update->WriteEventLogOnFailure = FALSE; Where = (LPBYTE) (Update + 1); Update->DnsHostName = (LPWSTR)Where; RtlCopyMemory( Where, DnsHostName, DnsHostNameSize ); Where += DnsHostNameSize; Update->NetbiosComputerName = (LPWSTR)Where; RtlCopyMemory( Where, ComputerName, NetbiosComputerNameSize ); Where += NetbiosComputerNameSize; Update->UncDcName = (LPWSTR)Where; RtlCopyMemory( Where, UncDcName, DcNameSize ); Where += DcNameSize; Update->NetbiosDomainName = (LPWSTR)Where; RtlCopyMemory( Where, NetbiosDomainName, NetbiosDomainNameSize ); Where += NetbiosDomainNameSize; Update->DnsDomainName = (LPWSTR)Where; NlCaptureDomainInfo( DomainInfo, Update->DnsDomainName, NULL ); Where += NL_MAX_DNS_LENGTH * sizeof(WCHAR) + sizeof(WCHAR); // // Either do the work now or queue it to a worker thread. // if ( Synchronous ) { // // On workstation where this call is synchronous, // log any error in the event log. // Update->WriteEventLogOnFailure = TRUE; (VOID) NlSetDsSPNWorker( Update ); } else { // // Queue it off to a worker thread. The update will take // place from a different thread, so we won't have interesting // deadlocks due to lookups. // NlPrint(( NL_MISC, "NlSetDsSPN: Queuing SPN update for %ws on %ws.\n", Update->DnsHostName, Update->UncDcName )); // // REVIEW: how do I wait for this worker to finish executing when the // service shuts down. // if ( !QueueUserWorkItem( NlSetDsSPNWorker, Update, 0 ) ) { LocalFree( Update ); return ERROR_NOT_ENOUGH_MEMORY ; } NetStatus = NO_ERROR; } return NetStatus; } NET_API_STATUS NET_API_FUNCTION DsrDeregisterDnsHostRecords ( IN LPWSTR ServerName OPTIONAL, IN LPWSTR DnsDomainName OPTIONAL, IN GUID *DomainGuid OPTIONAL, IN GUID *DsaGuid OPTIONAL, IN LPWSTR DnsHostName ) /*++ Routine Description: This function deletes all DNS entries associated with a particular NtDsDsa object. This routine does NOT delete A records registered by the DC. We have no way of finding out the IP addresses of the long gone DC. Only an Admin, Account Operator or Server Operator may call this function. Arguments: DnsDomainName - DNS domain name of the domain the DC was in. This need not be a domain hosted by this DC. If NULL, it is implied to be the DnsHostName with the leftmost label removed. DomainGuid - Domain Guid of the domain. If NULL, GUID specific names will not be removed. DsaGuid - GUID of the NtdsDsa object that will be deleted. If NULL, NtdsDsa specific names will not be removed. DnsHostName - DNS host name of the DC whose DNS records are being deleted. Return Value: NO_ERROR - Success. ERROR_NOT_SUPPORTED - The server specified is not a DC. ERROR_ACCESS_DENIED - The caller is not allowed to perform this operation. --*/ { NTSTATUS Status; NET_API_STATUS NetStatus; // // This APIis supported on DCs only // if ( NlGlobalMemberWorkstation ) { return ERROR_NOT_SUPPORTED; } // // Perform access validation on the caller // NetStatus = NetpAccessCheck( NlGlobalNetlogonSecurityDescriptor, // Security descriptor NETLOGON_CONTROL_ACCESS, // Desired access &NlGlobalNetlogonInfoMapping ); // Generic mapping if ( NetStatus != NERR_Success) { return ERROR_ACCESS_DENIED; } // // Notify the service that DNS records need to be deleted // Status = I_NetNotifyNtdsDsaDeletion ( DnsDomainName, DomainGuid, DsaGuid, DnsHostName ); if ( !NT_SUCCESS(Status) ) { NetStatus = NetpNtStatusToApiStatus( Status ); NlPrint(( NL_CRITICAL, "DsrDeregisterDnsHostRecords: Cannot I_NetNotifyNtdsDsaDeletion. %ld\n", NetStatus )); return NetStatus; } // // Everything was successful // return NO_ERROR; UNREFERENCED_PARAMETER( ServerName ); }