/*++ Copyright (c) 1987-1992 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 // Include files common to entire service // // Include files specific to this .c file // #include #include // PackSamXXX() #include // PackLsa .. #include // Security information #include // I_NetGetAnyDc #include // LsaOpenPolicy, etc #include // NetpAccessCheck #include #include // IS_PATH_SEPARATOR ... #include #include #include // // 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 NTSTATUS NlVerifyWorkstation( IN LPWSTR ServerName OPTIONAL ) /*++ Routine Description: Check the validity of the ServerName. Arguments: ServerName - Name of the server this code is executing on. Return Value: The status of the operation --*/ { // // Check the Servername to ensure he wants to talk to us. // if ( ServerName != NULL ) { if ( IS_PATH_SEPARATOR(ServerName[0]) && IS_PATH_SEPARATOR(ServerName[1])) { ServerName += 2; } if ( NlNameCompare( ServerName, NlGlobalUnicodeComputerName, NAMETYPE_COMPUTER ) != 0 ) { return STATUS_INVALID_COMPUTER_NAME; } } return STATUS_SUCCESS; } 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. --*/ { NTSTATUS Status; // // This API is not supported on workstations. // if ( NlGlobalRole == RoleMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // Check the primary name. // Status = NlVerifyWorkstation( PrimaryName ); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES, "NetrServerReqChallenge: ClientChallenge = %lx %lx\n", ((DWORD *) (ClientChallenge))[0], ((DWORD *) (ClientChallenge))[1])); #endif // BAD_ALIGNMENT // // Compute ServerChallenge to pass back to requestor // NlComputeChallenge(ServerChallenge); #ifdef BAD_ALIGNMENT NlPrint((NL_CHALLENGE_RES, "NetrServerReqChallenge: ServerChallenge = %lx %lx\n", ((DWORD *) (ServerChallenge))[0], ((DWORD *) (ServerChallenge))[1])); #endif // BAD_ALIGNMENT // // Add this entry into the server session table. // // Remember both challenges until the corresponding I_NetAuthenticate call. // Notice that both challenges are not yet SessionKey-encrypted // Status = NlInsertServerSession( ComputerName, SS_CHALLENGE, // challenge in progress 0, // No Account rid 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) ); } return Status; } 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 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. Return Value: The status of the operation. --*/ { NTSTATUS Status; NlPrint((NL_SESSION_SETUP, "NetrServerAuthenticate entered: " FORMAT_LPWSTR " on account " FORMAT_LPWSTR " (Negot: %lx)\n", ComputerName, AccountName, *NegotiatedFlags )); // // This API is not supported on workstations. // if ( NlGlobalRole == RoleMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // If CompatibilityMode is off, // disallow this function for downlevel servers. // if ( SecureChannelType == UasServerSecureChannel && !NlGlobalUasCompatibilityMode ) { NlPrint((NL_CRITICAL,"NetrServerAuthenticate " "from LM 2.x and not compatibility mode\n")); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Check the primary name. // Status = NlVerifyWorkstation( PrimaryName ); if ( !NT_SUCCESS( Status ) ) { NlPrint((NL_CRITICAL,"NetrServerAuthenticate: " "Error from NlVerifyWorkstation 0x%lx\n", Status)); goto Cleanup; } // // Ensure that this machine account is valid. // (Do everything but check the password.) // Status = NlCheckMachineAccount( AccountName, SecureChannelType ); if (!NT_SUCCESS( Status )) { NlPrint((NL_CRITICAL, "NetrServerAuthenticate: No machine account: " FORMAT_LPWSTR " on account " FORMAT_LPWSTR "\n", ComputerName, AccountName )); // // return more appropriate error // if ( SecureChannelType != UasServerSecureChannel && Status == STATUS_NO_SUCH_USER ) { Status = STATUS_NO_TRUST_SAM_ACCOUNT; } goto Cleanup; } // // Compute the NegotiatedFlags both sides support // *NegotiatedFlags &= NETLOGON_SUPPORTS_MASK; // // Authenticate the caller. // Status = NlAuthenticate( AccountName, SecureChannelType, ComputerName, ClientCredential, ServerCredential, *NegotiatedFlags ); if ( !NT_SUCCESS(Status) ) { NlPrint((NL_CRITICAL, "NetrServerAuthenticate: Bad password: " FORMAT_LPWSTR " on account " FORMAT_LPWSTR "\n", ComputerName, AccountName )); // // return more appropriate error // if ( SecureChannelType != UasServerSecureChannel && Status == STATUS_NO_SUCH_USER ) { Status = STATUS_NO_TRUST_SAM_ACCOUNT; } goto Cleanup; } Status = STATUS_SUCCESS; NlPrint((NL_SESSION_SETUP, "NetrServerAuthenticate returns Success: " FORMAT_LPWSTR " on account " FORMAT_LPWSTR " (Negot: %lx)\n", ComputerName, AccountName, *NegotiatedFlags )); // // Common exit point // Cleanup: // // If the request failed, be carefull to not leak authentication // information. // if ( !NT_SUCCESS(Status) ) { RtlZeroMemory( ServerCredential, sizeof(*ServerCredential) ); } // // write event log // if ( !NT_SUCCESS( Status ) ) { LPWSTR MsgStrings[3]; 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 ); } else { MsgStrings[2] = (LPWSTR) Status; NlpWriteEventlog( NELOG_NetlogonServerAuthFailed, EVENTLOG_ERROR_TYPE, (LPBYTE) & Status, sizeof(Status), MsgStrings, 3 | LAST_MESSAGE_IS_NTSTATUS ); } } return Status; } 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 1.0 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 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: 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 differently 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. For domain accounts and workstation accounts, the server being called may be a BDC in the specific domain. In that case, the BDC will validate the request and pass it on to the PDC of the domain using the server account secure channel. If the PDC of the domain is currently not available, the BDC will return STATUS_NO_LOGON_SERVERS. Since the UasNewPassword is passed encrypted by the session key, such a BDC will decrypt the UasNewPassword using the original session key and will re-encrypt it with the session key for its session to its PDC before passing the request on. 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. This field must be set to UasServerAccount to indicate a call from a downlevel 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 password for the server. This Password is generated by automatic means using random number genertaor seeded with the current Time It is assumed that the machine generated password was used as key to encrypt STD text and "sesskey" obtained via Challenge/Authenticate sequence was used to further encrypt it before passing to this api. i.e. UasNewPassword = E2(E1(STD_TXT, PW), SK) 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. --*/ { NTSTATUS Status; PSERVER_SESSION ServerSession; LM_OWF_PASSWORD OwfPassword; SAMPR_HANDLE UserHandle; NlPrint((NL_SESSION_SETUP, "NetrServerPasswordSet: Comp=" FORMAT_LPWSTR " Acc=" FORMAT_LPWSTR " Entered\n", ComputerName, AccountName )); // // This API is not supported on workstations. // if ( NlGlobalRole == RoleMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // Check the primary name. // Status = NlVerifyWorkstation( PrimaryName ); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } // // Get the Session key for this session. // LOCK_SERVER_SESSION_TABLE(); ServerSession = NlFindNamedServerSession( ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE(); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // 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) &ServerSession->SsSessionKey, &OwfPassword )) { UNLOCK_SERVER_SESSION_TABLE(); goto Cleanup; } // // now verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE(); 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 don't return STATUS_WRONG_PASSWORD here. Rather, the // solution is to set 'RefusePasswordChange' on all of the BDCs so they'll // catch the password change attempt before passing through. // if ( NlGlobalRefusePasswordChangeParameter && ServerSession->SsSecureChannelType == WorkstationSecureChannel && (ServerSession->SsNegotiatedFlags & NETLOGON_SUPPORTS_REFUSE_CHANGE_PWD) != 0 ){ Status = STATUS_WRONG_PASSWORD; UNLOCK_SERVER_SESSION_TABLE(); goto Cleanup; } UNLOCK_SERVER_SESSION_TABLE(); // // If this machine is a BDC, just pass the request on to the PDC. // if ( NlGlobalRole == RoleBackup ) { ENCRYPTED_LM_OWF_PASSWORD SessKeyEncrPassword; NETLOGON_AUTHENTICATOR OurAuthenticator; NETLOGON_AUTHENTICATOR OurReturnAuthenticator; // // Become a Writer of the ClientSession. // if ( !NlTimeoutSetWriterClientSession( NlGlobalClientSession, WRITER_WAIT_PERIOD ) ) { NlPrint((NL_CRITICAL, "NetrServerPasswordSet: Can't become writer of client session.\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) { NlResetWriterClientSession( NlGlobalClientSession ); Status = NlGlobalClientSession->CsConnectionStatus; goto Cleanup; } // // Encrypt the password again with the session key. // The PDC will decrypt it on the other side. // Status = RtlEncryptNtOwfPwdWithNtOwfPwd( &OwfPassword, (PNT_OWF_PASSWORD) &NlGlobalClientSession->CsSessionKey, &SessKeyEncrPassword) ; if ( !NT_SUCCESS( Status )) { NlPrint((NL_CRITICAL, "NetrServerPasswordSet: " "Cannot RtlEncryptNtOwfPwdWithNtOwfPed %lX\n", Status)); NlResetWriterClientSession( NlGlobalClientSession ); // Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } // // Build the Authenticator for this request to the PDC. // NlBuildAuthenticator( &NlGlobalClientSession->CsAuthenticationSeed, &NlGlobalClientSession->CsSessionKey, &OurAuthenticator); // // Change the password on the machine our connection is to. // Status = NlStartApiClientSession( NlGlobalClientSession, TRUE ); if ( NT_SUCCESS(Status) ) { Status = I_NetServerPasswordSet( NlGlobalClientSession->CsUncServerName, AccountName, AccountType, NlGlobalUnicodeComputerName, &OurAuthenticator, &OurReturnAuthenticator, &SessKeyEncrPassword); } // NOTE: This call may drop the secure channel behind our back (VOID) NlFinishApiClientSession( NlGlobalClientSession, TRUE ); // // Now verify primary's authenticator and update our seed // if ( Status == STATUS_ACCESS_DENIED || !NlUpdateSeed( &NlGlobalClientSession->CsAuthenticationSeed, &OurReturnAuthenticator.Credential, &NlGlobalClientSession->CsSessionKey) ) { Status = STATUS_TRUSTED_DOMAIN_FAILURE; NlSetStatusClientSession( NlGlobalClientSession, STATUS_ACCESS_DENIED ); NlResetWriterClientSession( NlGlobalClientSession ); goto Cleanup; } if ( !NT_SUCCESS( Status )) { // Status = STATUS_NO_LOGON_SERVERS; NlResetWriterClientSession( NlGlobalClientSession ); goto Cleanup; } NlResetWriterClientSession( NlGlobalClientSession ); // // If this machine is a PDC, // do the request locally. // } else { SAMPR_USER_INFO_BUFFER UserInfo; // // now get the requestor's current password // // // Open the user that represents this server. // Status = NlSamOpenNamedUser( AccountName, &UserHandle, NULL ); if ( !NT_SUCCESS(Status) ) { goto Cleanup; } // // If the authentication is from an NT client, // use the NT OWF Password, // otherwise, use the LM OWF password. // UserInfo.Internal1.PasswordExpired = FALSE; if ( AccountType == UasServerSecureChannel ) { UserInfo.Internal1.NtPasswordPresent = FALSE; UserInfo.Internal1.LmPasswordPresent = TRUE; UserInfo.Internal1.EncryptedLmOwfPassword = *((PENCRYPTED_LM_OWF_PASSWORD)(&OwfPassword)); } else { UserInfo.Internal1.LmPasswordPresent = FALSE; UserInfo.Internal1.NtPasswordPresent = TRUE; UserInfo.Internal1.EncryptedNtOwfPassword = *((PENCRYPTED_NT_OWF_PASSWORD)(&OwfPassword)); } Status = SamrSetInformationUser( UserHandle, UserInternal1Information, &UserInfo ); if (!NT_SUCCESS(Status)) { goto Cleanup; } (VOID) SamrCloseHandle( &UserHandle ); } 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) ); } NlPrint((NL_SESSION_SETUP, "NetrServerPasswordSet: Comp=" FORMAT_LPWSTR " Acc=" FORMAT_LPWSTR " returns 0x%lX\n", ComputerName, AccountName, Status )); return Status; } 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; case AddOrChangeGroup: Status = NlPackSamGroup( ChangeLogEntry->ObjectRid, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize ); break; case ChangeGroupMembership: + Status = NlPackSamGroupMember( ChangeLogEntry->ObjectRid, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize ); break; 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; case AddOrChangeAlias: Status = NlPackSamAlias( ChangeLogEntry->ObjectRid, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize ); break; case ChangeAliasMembership: Status = NlPackSamAliasMember( ChangeLogEntry->ObjectRid, &((DeltaArray->Deltas) [DeltaArray->CountReturned]), DBInfo, &BufferSize ); break; 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 1.0 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 NTLANMAN BDC or SAM member server 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; 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 ( NlGlobalRole == RoleMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // 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]; // // Check the primary name. // Status = NlVerifyWorkstation( PrimaryName ); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } RtlCopyMemory( &RunningSerialNumber, &NlDomainModifiedCount->ModifiedCount, sizeof(RunningSerialNumber)); OriginalSerialNumber.QuadPart = RunningSerialNumber.QuadPart; PackedSerialNumber.QuadPart = RunningSerialNumber.QuadPart; NlPrint((NL_SYNC, "NetrDatabaseDeltas: " FORMAT_LPWSTR " partial sync called by " FORMAT_LPWSTR " SerialNumber:%lx %lx.\n", DBInfo->DBName, ComputerName, RunningSerialNumber.HighPart, RunningSerialNumber.LowPart )); // // Retrieve the requestor's entry to get sessionkey // LOCK_SERVER_SESSION_TABLE(); ServerSession = NlFindNamedServerSession( ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE(); 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 ) { UNLOCK_SERVER_SESSION_TABLE(); Status = STATUS_ACCESS_DENIED; ServerSession = NULL; goto Cleanup; } // // Verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE(); 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(); 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(); // // 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, returning full " "required (%lx).\n", Status )); Status = STATUS_SYNCHRONIZATION_REQUIRED; IF_DEBUG( BREAKPOINT ) { NlAssert( FALSE ); } goto Cleanup; } 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 DBG if ( NlGlobalTrace & NL_ONECHANGE_REPL ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } #endif // DBG // // 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) Status; NlpWriteEventlog( NELOG_NetlogonPartialSyncCallFailed, EVENTLOG_WARNING_TYPE, (LPBYTE)&Status, sizeof(Status), MsgStrings, 2 | LAST_MESSAGE_IS_NTSTATUS ); } 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 ); } } // // 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 NT1.0 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 ); } NlPrint((NL_SYNC, "NetrDatabaseDeltas: " FORMAT_LPWSTR " returning (0x%lx) to " FORMAT_LPWSTR "\n", DBInfo->DBName, Status, ComputerName )); STOPSSIAPITIMER; 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 DBG if ( NlGlobalTrace & NL_ONECHANGE_REPL ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } #endif // DBG // // 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 ); } 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 DBG if ( NlGlobalTrace & NL_ONECHANGE_REPL ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } #endif // DBG } 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 1.0 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 a BDC server 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; 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 ( NlGlobalRole == RoleMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } 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; // // Check the primary name. // Status = NlVerifyWorkstation( PrimaryName ); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } NlPrint((NL_SYNC, "NetrDatabaseSync: " FORMAT_LPWSTR " full sync called by " FORMAT_LPWSTR " State: %ld Context: 0x%lx.\n", DBInfo->DBName, ComputerName, RestartState, *SyncContext )); // // Retrieve the requestor's entry to get sessionkey // LOCK_SERVER_SESSION_TABLE(); ServerSession = NlFindNamedServerSession( ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE(); 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 ) { UNLOCK_SERVER_SESSION_TABLE(); Status = STATUS_ACCESS_DENIED; ServerSession = NULL; goto Cleanup; } // // Verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE(); 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(); 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(); 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) Status; NlpWriteEventlog( NELOG_NetlogonFullSyncCallFailed, EVENTLOG_WARNING_TYPE, (LPBYTE)&Status, sizeof(Status), MsgStrings, 2 | LAST_MESSAGE_IS_NTSTATUS ); } 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 ); } // // 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 ); } 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; 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; PSERVER_SESSION ServerSession = NULL; LPWSTR MsgStrings[2]; DWORD BufferSize; PNETLOGON_DELTA_ENUM_ARRAY DeltaArray; SESSION_INFO SessionInfo; DEFSSIAPITIMER; INITSSIAPITIMER; STARTSSIAPITIMER; // // This API is not supported on workstations. // if ( NlGlobalRole == RoleMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // Initialization // ChangeLogEntry = (PCHANGELOG_ENTRY) OrigChangeLogEntry; if ( !NlValidateChangeLogEntry( ChangeLogEntry, ChangeLogEntrySize ) || ChangeLogEntry->DBIndex >= NUM_DBS ) { Status = STATUS_INVALID_PARAMETER; goto Cleanup; } NlPrint((NL_SYNC, "NetrDatabaseRedo: " FORMAT_LPWSTR " redo sync called by " FORMAT_LPWSTR " with this change log entry:\n", NlGlobalDBInfoArray[ChangeLogEntry->DBIndex].DBName, ComputerName )); #if DBG PrintChangeLogEntry( ChangeLogEntry ); #endif // DBG // // 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 = 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; // // Check the primary name. // Status = NlVerifyWorkstation( PrimaryName ); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } // // Retrieve the requestor's entry to get sessionkey // LOCK_SERVER_SESSION_TABLE(); ServerSession = NlFindNamedServerSession( ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE(); 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 ) { UNLOCK_SERVER_SESSION_TABLE(); Status = STATUS_ACCESS_DENIED; ServerSession = NULL; goto Cleanup; } // // Verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE(); 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(); 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(); // // 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 = NlGlobalDeleteDeltaType[ChangeLogEntry->DeltaType]; Status = NlPackSingleDelta( ChangeLogEntry, DeltaArray, &BufferSize, &SessionInfo, FALSE ); } Cleanup: // // write event log // if ( !NT_SUCCESS( Status ) ) { MsgStrings[0] = ComputerName; MsgStrings[1] = (LPWSTR) Status; NlpWriteEventlog( NELOG_NetlogonPartialSyncCallFailed, EVENTLOG_WARNING_TYPE, (LPBYTE)&Status, sizeof(Status), MsgStrings, 2 | LAST_MESSAGE_IS_NTSTATUS ); } 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 ); } } // // Free up locally allocated resources. // CleanupNoEventlog: // // If we weren't successful, // Don't return any deltas. // if ( !NT_SUCCESS(Status)) { 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 ); } 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; 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. --*/ { NTSTATUS Status; PSERVER_SESSION ServerSession = NULL; PCHANGELOG_ENTRY ChangeLogEntry = NULL; BOOL ChangelogLocked = FALSE; BUFFER_DESCRIPTOR BufferDescriptor; PDB_INFO DBInfo = &NlGlobalDBInfoArray[SAM_DB]; NETLOGON_SESSION_KEY SessionKey; DWORD DummyFlag; LONG RotateCount; BOOL RotateCountComputed = FALSE; // // This API is not supported on workstations. // if ( NlGlobalRole == RoleMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // If CompatibilityMode is off, // disallow this function for downlevel servers. // if ( !NlGlobalUasCompatibilityMode ) { Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Initialization // *CountReturned = 0; *TotalEntries = 0; *NextRecordId = *RecordId; // // Check the primary name. // Status = NlVerifyWorkstation( PrimaryName ); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } NlPrint((NL_SYNC, "NetrAccountDeltas: UAS partial sync called by " FORMAT_LPWSTR " with SerialNumber 0x%lx.\n", ComputerName, RecordId->SerialNumber )); // // we need to retrieve the requestor's entry to get sessionkey // LOCK_SERVER_SESSION_TABLE(); ServerSession = NlFindNamedServerSession( ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE(); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // allow this call to go through only on UasServerSecureChannel. // if( ServerSession->SsSecureChannelType != UasServerSecureChannel ) { UNLOCK_SERVER_SESSION_TABLE(); Status = STATUS_ACCESS_DENIED; ServerSession = NULL; goto Cleanup; } // // now verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE(); ServerSession = NULL; goto Cleanup; } SessionKey = ServerSession->SsSessionKey; UNLOCK_SERVER_SESSION_TABLE(); // // The requestor should have gotten his 'DomainModifiedCount' from // a UasChange record we broadcast. Therefore, It should be less than // or equal to the current DomainModifiedCount as set by SAM. If // not, force a sync. // // A Downlevel machine only has the least significant 32 bits of the // DomainModifiedCount. We'll just compare the least significant portion // knowing that a sync will be forced on wraparound. // LOCK_CHANGELOG(); ChangelogLocked = TRUE; if ( NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart < RecordId->SerialNumber ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } if ( NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart == RecordId->SerialNumber ) { Status = STATUS_SUCCESS; goto Cleanup; } *TotalEntries = NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart - RecordId->SerialNumber; // // Get a copy pointer to appropriate entry in change_log of primary. // Note that the RecordId contains last record received by client. // ChangeLogEntry = NlGetNextDownlevelChangeLogEntry( RecordId->SerialNumber ); if ( ChangeLogEntry == NULL ) { NlPrint((NL_CRITICAL, "NetrDatabaseDeltas: " "delta not found in cache, returning full required.\n" )); Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } UNLOCK_CHANGELOG(); ChangelogLocked = FALSE; // // Build a buffer descriptor describing the buffer the caller passed in. // if ( Buffer == NULL || BufferSize == 0 ) { Status = STATUS_BUFFER_TOO_SMALL; if ( ServerSession->SsFlags & SS_UAS_BUFFER_OVERFLOW ) { // // since this client has already overflowed the // databuffer once, he can't handle big size delta. // Status = STATUS_SYNCHRONIZATION_REQUIRED; } goto Cleanup; } BufferDescriptor.Buffer = Buffer; BufferDescriptor.AllocSize = BufferSize; BufferDescriptor.FixedDataEnd = BufferDescriptor.Buffer; BufferDescriptor.EndOfVariableData = BufferDescriptor.Buffer + BufferDescriptor.AllocSize; // // Loop through the delta table replicating each entry in the delta table. // for (;;) { // // If we've returned all the entries the caller wants, we're all done. // if ( (Count--) <= 0 ) { Status = STATUS_SUCCESS; goto Cleanup; } // // Put the data for the changelog entry into the user's buffer. // switch ( ChangeLogEntry->DeltaType ) { case AddOrChangeDomain: Status = NlPackUasDomain( &BufferDescriptor, DBInfo); break; case AddOrChangeGroup: Status = NlPackUasGroup( ChangeLogEntry->ObjectRid, &BufferDescriptor, DBInfo, &DummyFlag ); break; case AddOrChangeUser: // // If this is a user account whose membership in Domain Users // hasn't been communicated to the lanman BDC, // do so. // if ( ChangeLogEntry->Flags & CHANGELOG_DOMAINUSERS_CHANGED ) { Status = NlPackUasUserGroupMember( ChangeLogEntry->ObjectRid, &BufferDescriptor, DBInfo ); // // Otherwise this is just a added or changed user // } else { // // Compute the RotateCount for LogonHours // // Do it only once. // if ( !RotateCountComputed ) { if ( !NetpRotateLogonHoursPhase1( FALSE, &RotateCount ) ) { Status = STATUS_INTERNAL_ERROR; goto Cleanup; } RotateCountComputed = TRUE; } Status = NlPackUasUser( ChangeLogEntry->ObjectRid, &BufferDescriptor, DBInfo, &SessionKey, RotateCount ); } break; case ChangeGroupMembership: Status = NlPackUasGroupMember( ChangeLogEntry->ObjectRid, &BufferDescriptor, DBInfo ); break; case DeleteGroup: case DeleteUser: case RenameUser: case RenameGroup: if( ChangeLogEntry->Flags & CHANGELOG_NAME_SPECIFIED ) { Status = NlPackUasDelete( ChangeLogEntry->DeltaType, ChangeLogEntry->ObjectRid, (LPWSTR) (((LPBYTE)ChangeLogEntry) + sizeof(CHANGELOG_ENTRY)), &BufferDescriptor, DBInfo ); } else { Status = STATUS_NO_SUCH_USER; } break; case AddOrChangeAlias: case ChangeAliasMembership: case DeleteAlias: case RenameAlias: #define DELTA_RESERVED_OPCODE 255 { // // This record is incompatible with a downlevel // system, send a dummy record over to the downlevel system to // ensure the SerialNumber gets updated appropriately. // PUSHORT RecordSize; // ptr to record size field in record header. Status = NlPackUasHeader( DELTA_RESERVED_OPCODE, 0, &RecordSize, &BufferDescriptor ); } break; default: NlPrint((NL_CRITICAL, "NetrAccountDeltas: invalid delta type in change log\n")); Status = STATUS_SYNCHRONIZATION_REQUIRED; break; } // // If the buffer is too small to fit this entry, // If we returned at least one entry, simply tell the caller more // are available. // if (Status == STATUS_MORE_ENTRIES) { if (*CountReturned == 0) { if ( ServerSession->SsFlags & SS_UAS_BUFFER_OVERFLOW ) { // // since this client has already overflowed the // databuffer once, he can't handle big size delta. // Status = STATUS_SYNCHRONIZATION_REQUIRED; } else { Status = STATUS_BUFFER_TOO_SMALL; // // remember that this client data buffer overflowed. // ServerSession->SsFlags |= SS_UAS_BUFFER_OVERFLOW; } } goto Cleanup; } // // ?? The follow is not taken care for the down level. // // 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. // if ( Status != STATUS_SUCCESS ) { goto Cleanup; } // // Tell the caller he has another entry returned. // (*CountReturned)++; NextRecordId->SerialNumber = ChangeLogEntry->SerialNumber.LowPart; // // Free up used temp. record // NetpMemoryFree(ChangeLogEntry); ChangeLogEntry = NULL; // // If we've returned all the entries, we're all done. // LOCK_CHANGELOG(); ChangelogLocked = TRUE; ChangeLogEntry = NlGetNextDownlevelChangeLogEntry( NextRecordId->SerialNumber ); if ( ChangeLogEntry == NULL ) { Status = STATUS_SUCCESS; goto Cleanup; } UNLOCK_CHANGELOG(); ChangelogLocked = FALSE; // // If we're debugging replication, return only one change to the caller. // #if DBG if ( NlGlobalTrace & NL_ONECHANGE_REPL ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } #endif // DBG // // if the service is going down, stop packing deltas and // return to the caller. // if( NlGlobalTerminate ) { NlPrint((NL_CRITICAL, "NetrAccountDeltas is asked to return " "when the service is going down.\n")); Status = STATUS_MORE_ENTRIES; goto Cleanup; } } Cleanup: // // In the case where an user/group record was added and deleted // before the delta was made we will map the errors such that // the requesting machine will have to re-synchronize. It is // the easiest, not neccessarily the best, way to get both // machines in sync. // if (Status == STATUS_NO_SUCH_USER || Status == STATUS_NO_SUCH_GROUP || Status == STATUS_NONE_MAPPED ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; } // // reset buffer over flag in server session structure // if( (Status != STATUS_BUFFER_TOO_SMALL) && (ServerSession != NULL) && (ServerSession->SsFlags & SS_UAS_BUFFER_OVERFLOW) ) { ServerSession->SsFlags &= ~SS_UAS_BUFFER_OVERFLOW; } // // Free up locally allocated resources. // if ( !NT_SUCCESS( Status ) ) { *CountReturned = 0; } // // There are always at least as many as we returned // if ( *TotalEntries < *CountReturned ) { *TotalEntries = *CountReturned; } if ( ChangelogLocked ) { UNLOCK_CHANGELOG(); } if( ChangeLogEntry != NULL) { NetpMemoryFree( ChangeLogEntry ); } NlPrint((NL_SYNC, "NetrAccountDeltas: UAS partial sync returns %lx to " FORMAT_LPWSTR " Count: %ld Total:%ld\n", Status, ComputerName, *CountReturned, *TotalEntries )); return Status; UNREFERENCED_PARAMETER( Level ); } 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. --*/ { NTSTATUS Status; BUFFER_DESCRIPTOR BufferDescriptor; PSAMPR_DOMAIN_INFO_BUFFER DomainInfo = NULL; PSERVER_SESSION ServerSession = NULL; PCHAR Where; PDB_INFO DBInfo = &NlGlobalDBInfoArray[SAM_DB]; PSAM_SYNC_CONTEXT SamDBContext; LONG RotateCount; BOOL RotateCountComputed = FALSE; // // This API is not supported on workstations. // if ( NlGlobalRole == RoleMemberWorkstation ) { return STATUS_NOT_SUPPORTED; } // // If CompatibilityMode is off, // disallow this function for downlevel servers. // if ( !NlGlobalUasCompatibilityMode ) { Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // Initialization // *TotalEntries = 0; *CountReturned = 0; *NextReference = 0; // // Check the primary name. // Status = NlVerifyWorkstation( PrimaryName ); if ( !NT_SUCCESS( Status ) ) { goto Cleanup; } NlPrint((NL_SYNC, "NetrAccountSync: UAS full sync called by " FORMAT_LPWSTR " Reference= %lx.\n", ComputerName, Reference )); // // we need to retrieve the requestor's entry to get sessionkey // LOCK_SERVER_SESSION_TABLE(); ServerSession = NlFindNamedServerSession( ComputerName ); if (ServerSession == NULL) { UNLOCK_SERVER_SESSION_TABLE(); Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // allow this call to go through only on UasServerSecureChannel. // if( ServerSession->SsSecureChannelType != UasServerSecureChannel ) { UNLOCK_SERVER_SESSION_TABLE(); ServerSession = NULL; Status = STATUS_ACCESS_DENIED; goto Cleanup; } // // now verify the Authenticator and update seed if OK // Status = NlCheckAuthenticator( ServerSession, Authenticator, ReturnAuthenticator); if ( !NT_SUCCESS(Status) ) { UNLOCK_SERVER_SESSION_TABLE(); 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(); NlPrint((NL_CRITICAL, "NetrAccountSync: Concurrent call detected.\n" )); ServerSession = NULL; Status = STATUS_ACCESS_DENIED; goto Cleanup; } ServerSession->SsFlags |= SS_LOCKED; UNLOCK_SERVER_SESSION_TABLE(); // // Build a buffer descriptor describing the buffer the caller passed in. // if ( Buffer == NULL || BufferSize == 0 ) { Status = STATUS_BUFFER_TOO_SMALL; goto Cleanup; } BufferDescriptor.Buffer = Buffer; BufferDescriptor.AllocSize = BufferSize; BufferDescriptor.FixedDataEnd = BufferDescriptor.Buffer; BufferDescriptor.EndOfVariableData = BufferDescriptor.Buffer + BufferDescriptor.AllocSize; // // Compute the total number of entries. // // // Calculate total entries i.e. total records avaialable // modal rec + # group rec + # user rec (plus group membership // information) + 3 record for UasBuiltinGroups // Status = SamrQueryInformationDomain( DBInfo->DBHandle, DomainGeneralInformation, &DomainInfo ); if ( !NT_SUCCESS(Status) ) { DomainInfo = NULL; goto Cleanup; } *TotalEntries = 1 + (DomainInfo->General.GroupCount * 2) + DomainInfo->General.UserCount + 3; NlPrint((NL_SYNC, "NetrAccountSync: GroupCount: %ld UserCount: %ld\n", DomainInfo->General.GroupCount, DomainInfo->General.UserCount )); // // Warn the user if there are too many global groups in the domain. // // Lanman only support 255 groups. However this includes the global groups // LOCAL, ADMINS, USERS, and GUESTS. So only 251 real global groups are // allowed before the Lanman BDC goes into an infinite full sync. // if (!NlGlobalTooManyGlobalGroups && DomainInfo->General.GroupCount > 251 ) { NlGlobalTooManyGlobalGroups = TRUE; NlpWriteEventlog ( NELOG_NetlogonTooManyGlobalGroups, EVENTLOG_ERROR_TYPE, NULL, 0, NULL, 0 ); } SamIFree_SAMPR_DOMAIN_INFO_BUFFER( DomainInfo, DomainGeneralInformation ); // // If this is the first call, allocate and initialize the sync context. // if ( Reference == 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, SamDBContextType ); SamDBContext = &(ServerSession->SsSync->DBContext.Sam); SamDBContext->SyncState = GroupState; SamDBContext->SyncSerial = 1; // // On the first record only, return the current serial number of // the database so the caller can use it as a description of the // database. // LastRecordId->SerialNumber = NlGlobalChangeLogDesc.SerialNumber[SAM_DB].LowPart; if (!RtlTimeToSecondsSince1970( &DBInfo->CreationTime, &LastRecordId->TimeCreated )) { NlPrint((NL_CRITICAL, "NetAccountSync: DomainCreationTime can't be converted\n" )); LastRecordId->TimeCreated = 0; } Where = LastRecordId->ComputerName; NetpLogonPutOemString( NlGlobalAnsiComputerName, sizeof(LastRecordId->ComputerName), &Where ); // // Put the description of the Domain at the front of the buffer for the // first call. // Status = NlPackUasDomain( &BufferDescriptor, DBInfo ); if ( Status != STATUS_SUCCESS ) { Status = STATUS_BUFFER_TOO_SMALL; goto Cleanup; } (*CountReturned)++; } else { if ( (ServerSession->SsSync == NULL) || (ServerSession->SsSync->DBContextType != SamDBContextType) ) { Status = STATUS_SYNCHRONIZATION_REQUIRED; goto Cleanup; } SamDBContext = &(ServerSession->SsSync->DBContext.Sam); if ( SamDBContext->SyncSerial != Reference ) { 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 groups from SAM in a // single call. The second part puts a single entry into the buffer // returned to the caller. // while ( SamDBContext->SyncState != SamDoneState ) { // // 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, // we're all done. // if ( SamDBContext->SamAllDone ) { SamDBContext->SamEnumHandle = 0; SamDBContext->Index = 0; SamDBContext->Count = 0; SamDBContext->SamAllDone = FALSE; SamDBContext->UasBuiltinGroups = 0; if (SamDBContext->SyncState == GroupState ) { SamDBContext->SyncState = UasBuiltinGroupState; } else if (SamDBContext->SyncState == UasBuiltinGroupState ) { SamDBContext->SyncState = UserState; } else if (SamDBContext->SyncState == UserState ) { SamDBContext->SyncState = SamDoneState; Status = STATUS_SUCCESS; } break; } // // Do the actual enumeration // if (SamDBContext->SyncState == GroupState) { Status = SamrEnumerateGroupsInDomain( DBInfo->DBHandle, &SamDBContext->SamEnumHandle, &SamDBContext->SamEnum, SAM_SYNC_PREF_MAX, &SamDBContext->Count ); } else if (SamDBContext->SyncState == UasBuiltinGroupState) { SamDBContext->SamEnum = NULL; SamDBContext->Count = UAS_BUILTIN_GROUPS_COUNT; Status = STATUS_SUCCESS; } else if (SamDBContext->SyncState == UserState ) { Status = SamrEnumerateUsersInDomain( DBInfo->DBHandle, &SamDBContext->SamEnumHandle, 0, // enumerate all accounts. &SamDBContext->SamEnum, SAM_SYNC_PREF_MAX, &SamDBContext->Count ); } else { NlPrint((NL_CRITICAL, "NetrAccountSync: Invalid state: %ld\n", SamDBContext->SyncState )); } if ( !NT_SUCCESS( Status ) ) { SamDBContext->SamEnum = NULL; goto Cleanup; } #if DBG if( SamDBContext->SamEnum != NULL ) { NlAssert( SamDBContext->Count == SamDBContext->SamEnum->EntriesRead ); } #endif // DBG // // If SAM says there is more information, // just ensure he returned something to us on this call. // if ( Status == STATUS_MORE_ENTRIES ) { if ( SamDBContext->Count == 0 ) { Status = STATUS_INTERNAL_ERROR; goto Cleanup; } // // 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 = NlPackUasGroup( SamDBContext->SamEnum-> Buffer[SamDBContext->Index].RelativeId, &BufferDescriptor, DBInfo, &SamDBContext->UasBuiltinGroups ); } else if (SamDBContext->SyncState == UasBuiltinGroupState ) { Status = NlPackUasBuiltinGroup( SamDBContext->Index, &BufferDescriptor, &SamDBContext->UasBuiltinGroups ); } else if (SamDBContext->SyncState == UserState ) { BUFFER_DESCRIPTOR SavedBufferDescriptor; // // Compute the RotateCount for LogonHours // // Do it only once. // if ( !RotateCountComputed ) { if ( !NetpRotateLogonHoursPhase1( FALSE, &RotateCount ) ) { Status = STATUS_INTERNAL_ERROR; goto Cleanup; } RotateCountComputed = TRUE; } // // save buffer info so that we can restore it when // we can't place user record and its group // membership record in single transmit buffer. // SavedBufferDescriptor = BufferDescriptor; Status = NlPackUasUser( SamDBContext->SamEnum-> Buffer[SamDBContext->Index].RelativeId, &BufferDescriptor, DBInfo, &ServerSession->SsSessionKey, RotateCount ); // // if we have successfully packed the user record, then // place its group membership record immediately. // if ( Status == STATUS_SUCCESS ) { Status = NlPackUasUserGroupMember( SamDBContext->SamEnum-> Buffer[SamDBContext->Index].RelativeId, &BufferDescriptor, DBInfo ); if ( Status == STATUS_SUCCESS ) { // // increment record count // (*CountReturned)++; } else { BufferDescriptor = SavedBufferDescriptor; } } } // // If the record was properly packed, // just go on to the next record. // if ( Status != STATUS_SUCCESS ) { goto Cleanup; } SamDBContext->Index ++; (*CountReturned)++; // // If we're debugging replication, return only one change to the caller. // #if DBG if ( NlGlobalTrace & NL_ONECHANGE_REPL ) { Status = STATUS_MORE_ENTRIES; goto Cleanup; } #endif // DBG // // if the service is going down, stop packing records and // return to the caller. // if( NlGlobalTerminate ) { NlPrint((NL_CRITICAL, "NetrAccountSync 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 ) ) { if ( Status == STATUS_MORE_ENTRIES ) { *NextReference = SamDBContext->SyncSerial; } else { *NextReference = (ULONG) -1; } } else { *CountReturned = 0; *NextReference = 0; } // // Unlock the server session entry if we've locked it. // 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; } NlUnlockServerSession( ServerSession ); } NlPrint((NL_SYNC, "NetrAccountSync: UAS full sync returns %lx to " FORMAT_LPWSTR "\n", Status, ComputerName )); return Status; UNREFERENCED_PARAMETER( Level ); } 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 DBG case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: case NETLOGON_CONTROL_TRUNCATE_LOG: case NETLOGON_CONTROL_BREAKPOINT: #endif // DBG 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_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). QueryLevel - Indicates what information should be returned from the Netlogon Service. Must be 1. InputData - According to the function code specified this parameter will carry input data. NETLOGON_CONTROL_REDISCOVER and NETLOGON_CONTROL_TC_QUERY 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 Flags = 0; DWORD i; DWORD InfoSize; ACCESS_MASK DesiredAccess; UNICODE_STRING DomainName; PCLIENT_SESSION ClientSession = NULL; LPWSTR TDCName = NULL; LPWSTR TrustedDomainName = NULL; WCHAR TDCBuffer[UNCLEN+1]; UNREFERENCED_PARAMETER( ServerName ); // // 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_FIND_USER: #if DBG case NETLOGON_CONTROL_SET_DBFLAG: #endif // DBG 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: 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_FIND_USER: #if DBG case NETLOGON_CONTROL_BREAKPOINT: case NETLOGON_CONTROL_SET_DBFLAG: case NETLOGON_CONTROL_TRUNCATE_LOG: case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: #endif // DBG 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: NlPrint((NL_MISC, "QUERY function received.\n" )); break; // // Force a replication on a BDC. // case NETLOGON_CONTROL_REPLICATE: // // This FunctionCode is only valid on a BDC // if ( NlGlobalRole != RoleBackup ) { NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } // // Force a replicate on all databases. // NlPrint((NL_SYNC, "Force PARTIAL SYNC function received.\n" )); EnterCriticalSection( &NlGlobalDbInfoCritSect ); for( i = 0; i < NUM_DBS; i++ ) { NlGlobalDBInfoArray[i].UpdateRqd = TRUE; } LeaveCriticalSection( &NlGlobalDbInfoCritSect ); // // Start the replicator now. // (VOID) NlStartReplicatorThread( 0 ); break; // // Force a full synchronize on a BDC. // case NETLOGON_CONTROL_SYNCHRONIZE: // // This FunctionCode is only valid on a BDC // if ( NlGlobalRole != RoleBackup ) { NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } // // Force a SYNC on all databases. // NlPrint((NL_SYNC, "Force FULL SYNC function received.\n" )); for( i = 0; i < NUM_DBS; i++ ) { (VOID) NlForceStartupSync( &NlGlobalDBInfoArray[i] ); // // Do a complete full sync (don't restart it). // NlSetFullSyncKey( i, NULL ); } // // Stop the replicator. // // It might be in the middle of a full sync. This'll force it to // start over again. // // It might be waiting for 5 minutes to start it's next iteration. // This'll force it to start NOW. // // It might have marked that it's already done a full sync. This'll // force it to do another one. // NlStopReplicator(); // // Start the replicator now. // (VOID) NlStartReplicatorThread( 0 ); break; // // Force a PDC to broadcast a database change record. // case NETLOGON_CONTROL_PDC_REPLICATE: // // This FunctionCode is only valid on a PDC // if ( NlGlobalRole != RolePrimary ) { NetStatus = ERROR_NOT_SUPPORTED; goto Cleanup; } // // Simply send the announcement. Any BDC that is out of date // will replicate any changes. // NlPrint((NL_SYNC, "PDC REPLICATE function received.\n" )); NlPrimaryAnnouncement( ANNOUNCE_FORCE ); break; // // Force to rediscover trusted domain DCs. // case NETLOGON_CONTROL_REDISCOVER: NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_REDISCOVER function received.\n" )); NlAssert( InputData->TrustedDomainName != NULL ); if( InputData->TrustedDomainName == NULL ) { NlPrint((NL_CRITICAL, "NetrLogonControl called with " "function code NETLOGON_CONTROL_REDISCOVER " "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( &DomainName ); if( ClientSession == NULL ) { NlPrint((NL_CRITICAL, "NetrLogonControl can't find the client structure of " "the domain %wZ specified.\n", &DomainName )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } // // Force Discovery of a DC // if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrint((NL_CRITICAL, "NetrLogonControl2: Can't become writer of client session.\n" )); NetStatus = ERROR_NO_LOGON_SERVERS; goto Cleanup; } else { NlSetStatusClientSession( ClientSession, STATUS_NO_LOGON_SERVERS ); Status = NlDiscoverDc( ClientSession, DT_Synchronous ); NlResetWriterClientSession( ClientSession ); if ( !NT_SUCCESS(Status) ) { NlPrint((NL_CRITICAL, "NetrLogonControl: %wZ: Discovery failed %lx\n", &ClientSession->CsDomainName, Status )); NetStatus = NetpNtStatusToApiStatus( Status ); goto Cleanup; } } break; case NETLOGON_CONTROL_TC_QUERY: NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_TC_QUERY function received.\n" )); 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: { PLIST_ENTRY ListEntry; NlPrint((NL_SESSION_SETUP , "NETLOGON_CONTROL_TRANSPORT_NOTIFY function received.\n" )); EnterCriticalSection( &NlGlobalDcDiscoveryCritSect ); LOCK_TRUST_LIST(); // // Mark each entry to indicate we've not tried to authenticate recently // if ( NlGlobalClientSession != NULL ) { if ( NlGlobalClientSession->CsState != CS_AUTHENTICATED ) { NlPrint(( NL_SESSION_SETUP, " %wZ: Zero LastAuth\n", &NlGlobalClientSession->CsDomainName )); NlGlobalClientSession->CsLastAuthenticationTry.QuadPart = 0; } } for ( ListEntry = NlGlobalTrustList.Flink ; ListEntry != &NlGlobalTrustList ; ListEntry = ListEntry->Flink) { ClientSession = CONTAINING_RECORD( ListEntry, CLIENT_SESSION, CsNext ); if ( ClientSession->CsState != CS_AUTHENTICATED ) { NlPrint(( NL_SESSION_SETUP, " %wZ: Zero LastAuth\n", &ClientSession->CsDomainName )); ClientSession->CsLastAuthenticationTry.QuadPart = 0; } } UNLOCK_TRUST_LIST(); LeaveCriticalSection( &NlGlobalDcDiscoveryCritSect ); ClientSession = NULL; break; } // // Find a user in one of the trusted domains. // case NETLOGON_CONTROL_FIND_USER: NlPrint((NL_MISC, "NETLOGON_CONTROL_FIND_USER function received for %ws.\n", InputData->UserName )); // // 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 // ClientSession = NlPickDomainWithAccount ( InputData->UserName, USER_NORMAL_ACCOUNT | USER_MACHINE_ACCOUNT_MASK ); break; #if DBG // // Force a breakpoint // case NETLOGON_CONTROL_BREAKPOINT: KdPrint(( "I_NetLogonControl Break Point\n")); DbgBreakPoint(); break; // // Change the debug flags // case NETLOGON_CONTROL_SET_DBFLAG: NlGlobalTrace = InputData->DebugFlag; NlPrint((NL_MISC,"NlGlobalTrace is set to %lx\n", NlGlobalTrace )); 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: NetStatus = NlBackupChangeLogFile(); NlPrint((NL_MISC, "BACKUP_CHANGE_LOG function received, (%ld).\n", NetStatus )); break; #endif // DBG // // 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: if (ClientSession == NULL) { NetStatus = NERR_UserNotFound; goto Cleanup; } // // Capture the name of the server // (even if it is an empty string.) // Status = NlCaptureServerClientSession( ClientSession, TDCBuffer ); TDCName = NetpAllocWStrFromWStr( TDCBuffer ); if ( TDCName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } QueryInformation->NetlogonInfo4->netlog4_trusted_dc_name = TDCName; // // Capture the name of the domain. // TrustedDomainName = NetpAllocWStrFromWStr( ClientSession->CsDomainName.Buffer ); if ( TrustedDomainName == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } 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 = 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: if( ClientSession == NULL ) { NlAssert( InputData->TrustedDomainName != NULL ); if( InputData->TrustedDomainName == NULL ) { NlPrint((NL_CRITICAL, "NetrLogonControl called to query at info " "level 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( &DomainName ); if( ClientSession == NULL ) { NlPrint((NL_CRITICAL, "NetrLogonControl can't find the client structure of " "the domain %wZ specified.\n", &DomainName )); NetStatus = ERROR_NO_SUCH_DOMAIN; goto Cleanup; } } // // Capture the name of the server // (even if it is an empty string.) // Status = NlCaptureServerClientSession( ClientSession, TDCBuffer ); QueryInformation->NetlogonInfo2->netlog2_tc_connection_status = NetpNtStatusToApiStatus(Status); TDCName = NetpAllocWStrFromWStr( TDCBuffer ); 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): // // If this is a BDC, query how replication is going. // if ( NlGlobalRole == RoleBackup ) { // // If this is a BDC tell the caller whether the replicator is running, // EnterCriticalSection( &NlGlobalReplicatorCritSect ); if ( IsReplicatorRunning() ) { Flags |= NETLOGON_REPLICATION_IN_PROGRESS; } LeaveCriticalSection( &NlGlobalReplicatorCritSect ); EnterCriticalSection( &NlGlobalDbInfoCritSect ); for( i = 0; i < NUM_DBS; i++ ) { if ( NlGlobalDBInfoArray[i].UpdateRqd ) { Flags |= NETLOGON_REPLICATION_NEEDED; } if ( NlGlobalDBInfoArray[i].FullSyncRequired ) { Flags |= NETLOGON_FULL_SYNC_REPLICATION; } if ( NlGlobalRedoLogDesc.EntryCount[i] != 0 ) { Flags |= NETLOGON_REDO_NEEDED | NETLOGON_REPLICATION_NEEDED; } } LeaveCriticalSection( &NlGlobalDbInfoCritSect ); } // // Fill in the return buffer // QueryInformation->NetlogonInfo1->netlog1_flags = Flags; if ( NlGlobalRole == RolePrimary ) { QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status = NERR_Success; } else { QueryInformation->NetlogonInfo1->netlog1_pdc_connection_status = NetpNtStatusToApiStatus( NlGlobalClientSession->CsConnectionStatus); } break; default: break; } NetStatus = NERR_Success; // // Free up locally used resources. // Cleanup: if( ClientSession != NULL ) { NlUnrefClientSession( ClientSession ); } 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 ); } } return NetStatus; } 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; UNICODE_STRING DomainNameString; UNICODE_STRING UncDcName; UNREFERENCED_PARAMETER( ServerName ); // // Fill in the primary domain name if the caller didn't specify one. // if ( DomainName == NULL || *DomainName == L'\0' ) { RtlInitUnicodeString( &DomainNameString, NlGlobalUnicodeDomainName ); } else { RtlInitUnicodeString( &DomainNameString, DomainName ); } Status = I_NetGetAnyDCName( &DomainNameString, &UncDcName ); if ( !NT_SUCCESS(Status) ) { return NetpNtStatusToApiStatus(Status); } *Buffer = UncDcName.Buffer; return NERR_Success; } NTSTATUS I_NetGetAnyDCName ( IN PUNICODE_STRING DomainName, OUT PUNICODE_STRING 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. 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. Arguments: DomainName - name of domain UncDcName - 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 MIDL_user_free. 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; PCLIENT_SESSION ClientSession = NULL; ULONG DiscoveryDone = FALSE; UNICODE_STRING UncDcNameString; WCHAR UncDcName[UNCLEN+1]; LSA_HANDLE LsaHandle = NULL; OBJECT_ATTRIBUTES ObjectAttributes; PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomain = NULL; PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomain = NULL; RtlInitUnicodeString( Buffer, NULL ); // // If netlogon is not running (LSA is calling us directly), // don't let the caller proceed. if ( NlGlobalChangeLogNetlogonState != NetlogonStarted ) { Status = STATUS_NETLOGON_NOT_STARTED; goto Cleanup; } // // On the PDC or BDC, // find the Client session for the domain. // On workstations, // find the primary domain client session. // ClientSession = NlFindNamedClientSession( DomainName ); if ( ClientSession == NULL ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: %wZ: No such trusted domain\n", DomainName )); Status = STATUS_NO_SUCH_DOMAIN; goto Cleanup; } // // 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; } // // Discover a DC // if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } // Check again now that we're the writer if ( ClientSession->CsState == CS_IDLE ) { Status = NlDiscoverDc( ClientSession, DT_Synchronous ); if ( !NT_SUCCESS(Status) ) { NlResetWriterClientSession( ClientSession ); NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: %wZ: Discovery failed %lx\n", DomainName, Status )); goto Cleanup; } DiscoveryDone = TRUE; } NlResetWriterClientSession( ClientSession ); } // // Capture a copy of the DC the session is to. // Status = NlCaptureServerClientSession( ClientSession, UncDcName ); if ( !NT_SUCCESS(Status) ) { continue; } // // Cleanup resources from the previous iteration of the loop // if ( LsaHandle != NULL ) { (VOID) LsaClose( LsaHandle ); LsaHandle = NULL; } if ( AccountDomain != NULL ) { (VOID) LsaFreeMemory( AccountDomain ); AccountDomain= NULL; } if ( PrimaryDomain != NULL ) { (VOID) LsaFreeMemory( PrimaryDomain ); PrimaryDomain = NULL; } // // Open the policy database on the machine and query its primary and // account domains. // RtlInitUnicodeString( &UncDcNameString, UncDcName ); InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL ); Status = LsaOpenPolicy( &UncDcNameString, &ObjectAttributes, POLICY_VIEW_LOCAL_INFORMATION, &LsaHandle ); if ( !NT_SUCCESS(Status) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: %wZ" ": LsaOpenPolicy failed on " FORMAT_LPWSTR " %lx\n", DomainName, UncDcName, Status )); Status = STATUS_NO_LOGON_SERVERS; if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } NlSetStatusClientSession( ClientSession, Status ); NlResetWriterClientSession( ClientSession ); continue; } Status = LsaQueryInformationPolicy( LsaHandle, PolicyPrimaryDomainInformation, &PrimaryDomain ); if ( !NT_SUCCESS(Status) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: %wZ: LsaQueryInformationPolicy " "(Primary) failed on " FORMAT_LPWSTR " %lx\n", DomainName, UncDcName, Status )); Status = STATUS_NO_LOGON_SERVERS; if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } NlSetStatusClientSession( ClientSession, Status ); NlResetWriterClientSession( ClientSession ); continue; } Status = LsaQueryInformationPolicy( LsaHandle, PolicyAccountDomainInformation, &AccountDomain ); if ( !NT_SUCCESS(Status) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: %wZ: LsaQueryInformationPolicy " "(Account) failed on " FORMAT_LPWSTR " %lx\n", DomainName, UncDcName, Status )); Status = STATUS_NO_LOGON_SERVERS; if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } NlSetStatusClientSession( ClientSession, Status ); NlResetWriterClientSession( ClientSession ); continue; } // // Ensure the machine is really a member of the queried domain. // if ( !RtlEqualDomainName( DomainName, &PrimaryDomain->Name ) ) { Status = STATUS_DOMAIN_TRUST_INCONSISTENT; NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: %wZ: " "Domain name mismatch %wZ from " FORMAT_LPWSTR ".\n", DomainName, &PrimaryDomain->Name, UncDcName )); if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } NlSetStatusClientSession( ClientSession, Status ); NlResetWriterClientSession( ClientSession ); continue; } // // Ensure the machine is still a DC. // if ( AccountDomain->DomainSid == NULL || PrimaryDomain->Sid == NULL || !RtlEqualSid( AccountDomain->DomainSid, PrimaryDomain->Sid ) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: %wZ: " "Not-LanManNt mismatch from " FORMAT_LPWSTR ".\n", DomainName, UncDcName )); Status = STATUS_DOMAIN_TRUST_INCONSISTENT; if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } NlSetStatusClientSession( ClientSession, Status ); NlResetWriterClientSession( ClientSession ); continue; } // // Ensure the domain has the right sid. // if ( PrimaryDomain->Sid == NULL || !RtlEqualSid( ClientSession->CsDomainId, PrimaryDomain->Sid ) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: %wZ: " "Sid mismatch from " FORMAT_LPWSTR ".\n", DomainName, UncDcName )); Status = STATUS_DOMAIN_TRUST_INCONSISTENT; if ( !NlTimeoutSetWriterClientSession( ClientSession, WRITER_WAIT_PERIOD ) ) { NlPrint((NL_CRITICAL, "I_NetGetAnyDcName: Can't become writer of client session.\n" )); Status = STATUS_NO_LOGON_SERVERS; goto Cleanup; } NlSetStatusClientSession( ClientSession, Status ); NlResetWriterClientSession( ClientSession ); continue; } // // We've found it. // Status = STATUS_SUCCESS; } while ( !NT_SUCCESS(Status) && !DiscoveryDone ); // // 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; } if ( ClientSession != NULL ) { NlUnrefClientSession( ClientSession ); } if ( LsaHandle != NULL ) { (VOID) LsaClose( LsaHandle ); } if ( AccountDomain != NULL ) { (VOID) LsaFreeMemory( AccountDomain ); } if ( PrimaryDomain != NULL ) { (VOID) LsaFreeMemory( PrimaryDomain ); } // // Return the DCName to the caller. // if ( NT_SUCCESS(Status) ) { LPWSTR AllocatedUncDcName; AllocatedUncDcName = NetpAllocWStrFromWStr( UncDcName ); if ( AllocatedUncDcName == NULL ) { Status = STATUS_NO_MEMORY; } else { RtlInitUnicodeString( Buffer, AllocatedUncDcName ); } } return Status; }