4.1.15.3 Server Behavior of the IDL_DRSInterDomainMove Method

Informative summary of behavior: IDL_DRSInterDomainMove is used during a cross-NC move operation. This is a special object move operation because it involves moving an object from one DC into another. A normal move operation moves the object within one NC on one DC; a cross-NC move involves two DCs. IDL_DRSInterDomainMove is an intermediate step in the cross-NC move operation, which is initiated by an LDAP call. The IDL_DRSInterDomainMove call is done by the "source" DC to the "target" DC in order to move the object with all of its data from one NC replica into another.

Note IDL_DRSInterDomainMove transfers data that is normally not readable by the end user (such as password hashes and other secrets).

During the move, the ENTINF structure that contains the object data is constructed by the source DC and passed to the target DC. The target DC enforces certain constraints, transforms the data according to the processing rules, and then either creates the object in its NC replica or updates the existing object. For more information on cross-NC move operations, see [MS-ADTS] section 3.1.1.5.4.2.

 ULONG
 IDL_DRSInterDomainMove(
     [in, ref] DRS_HANDLE hDrs,
     [in] DWORD dwInVersion,
     [in, ref, switch_is(dwInVersion)] 
         DRS_MSG_MOVEREQ *pmsgIn,
     [out, ref] DWORD *pdwOutVersion,
     [out, ref, switch_is(*pdwOutVersion)] 
         DRS_MSG_MOVEREPLY *pmsgOut)
  
 msgIn: DRS_MSG_MOVEREQ_V2
 lastPrefixTableEntry: PrefixTableEntry
 prefixTable: PrefixTable
 dwErr: DWORD
 clientCreds: ClientAuthorizationInfo
 callerCreds: ClientAuthorizationInfo
 O: ENTINF
 existingObj: DSName
 attribute: ATTRTYP
 proxyEpoch: DWORD
  
 ValidateDRSInput(hDrs, 10)
  
 pdwOutVersion^ := 2
 msgOut^.V2.win32error := ERROR_DS_GENERIC_ERROR
 msgOut^.V2.pAddedName := null
 if dwInVersion ≠ 2 then
   return ERROR_DS_DRA_INVALID_PARAMETER
 endif
 msgIn := pmsgIn^.V2
 if msgIn.pExpectedTargetNC ≠ DefaultNC() then
   return ERROR_DS_DST_NC_MISMATCH
 endif
 if msgIn.PrefixTable.PrefixCount < 1 then
   return ERROR_SCHEMA_MISMATCH
 endif
  
 /* Remember last prefix table entry, and remove it from the prefix
  * table.*/
 lastPrefixTableEntry :=
   msgIn.PrefixTable.pPrefixEntry[msgIn.PrefixTable.PrefixCount]
 msgIn.PrefixTable.PrefixCount := msgIn.PrefixTable.PrefixCount-1
  
 /* Perform a binary comparison of the value from the last
  * prefixTable entry with the SchemaInfo.*/
 if lastPrefixTableEntry.oid ≠ SchemaInfo() then
   return ERROR_SCHEMA_MISMATCH
 endif
  
 prefixTable := AbstractPTFromConcretePT(msgIn.PrefixTable)
  
 /* Convert client creds into ClientAuthorizationInfo format. */
 dwErr := AuthorizationInfoFromClientCredentials(msgIn.pClientCreds,
     clientCreds)
 if dwErr ≠ ERROR_SUCCESS then
   return dwErr
 endif
  
 /* Check that the caller (the "source" DC) is actually a DC by
  * checking Enterprise Domain Controllers SID in its token. */
 callerCreds := GetCallerAuthorizationInfo()
 if not CheckGroupMembership(callerCreds, SidFromStringSid("S-1-5-9"))
       then
   return ERROR_DS_DRA_ACCESS_DENIED
 endif
  
 /* Validate input ENTINF. */
 O := msgIn.pSrcObject^
  
 if  ADS_UF_SERVER_TRUST_ACCOUNT in
          ENTINF_GetValue(
              O, userAccountControl, prefixTable) or
     ADS_UF_INTERDOMAIN_TRUST_ACCOUNT in
          ENTINF_GetValue(
              O, userAccountControl, prefixTable) then
   /* Disallowed to move DC accounts and trust objects. */
   return ERROR_DS_ILLEGAL_XDOM_MOVE_OPERATION
 endif
 existingObj := select one obj from all where 
     (obj!distinguishedName = ENTINF_GetValue(O, distinguishedName,
       prefixTable))
 if existingObj ≠ null and
    existingObj!objectGUID ≠ 
        ENTINF_GetValue(O, objectGUID, prefixTable) then
   /* There's already an object with the same DN but different GUID.*/
   return ERROR_DS_SRC_GUID_MISMATCH
 endif
  
 existingObj := select one obj from all where 
     (obj!objectGUID = ENTINF_GetValue(O, objectGUID, prefixTable))
 if existingObj ≠ null and existingObj!proxiedObjectName ≠
     ENTINF_GetValue(O, proxiedObjectName, prefixTable) then
   /* There's already an object with the same guid, 
    * but proxiedObjectName is different - not allowed. */
   return ERROR_DS_EPOCH_MISMATCH
 endif
  
 if IsApplicationNC(GetObjectNC(O.pName^)) then
   return ERROR_DS_INTERNAL_FAILURE
 endif
  
 /* Scan through the ENTINF and throw away any attributes that are not
  * supposed to be moved. */
 foreach attribute in ENTINF_EnumerateAttributes(O, prefixTable)
   if AttrIsBacklink(attribute) or AttrIsNonReplicated(attribute) or
       AttrIsConstructed(attribute) then
     ENTINF_SetValue(O, attribute, null, prefixTable)
   endif
   if attribute in {
       adminCount, badPasswordTime, badPwdCount, creationTime,
       distinguishedName, domainReplica, instanceType,
       isCriticalSystemObject, isDeleted, lastLogoff, lastLogon,
       lastLogonTimestamp, lockoutTime, logonCount, modifiedCount,
       modifiedCountAtLastProm, msDS-Cached-Membership,
       msDS-Cached-Membership-Time-Stamp, msDS-Site-Affinity, nextRid,
       nTSecurityDescriptor, objectCategory, operatorCount,
       primaryGroupID, proxiedObjectName, replPropertyMetaData,
       revision, rid, sAMAccountType, serverState, subRefs,
       systemFlags, uASCompat, uSNChanged, uSNCreated,
       uSNDSALastObjRemoved, uSNLastObjRem, whenChanged, whenCreated}
       then
     ENTINF_SetValue(O, attribute, null, prefixTable)
   endif
 endfor
  
 if ENTINF_GetValue(O, userAccountControl, prefixTable) ≠ null then
   /* Reset lockout bit. */
   ENTINF_SetValue(O, userAccountControl, 
      ENTINF_GetValue(O, userAccountControl) - {ADS_UF_LOCKOUT},
        prefixTable)
 endif
 if ENTINF_GetValue(O, pwdLastSet, prefixTable) ≠ null and
     ENTINF_GetValue(O, pwdLastSet, prefixTable) ≠ 0 then
   /* If pwdLastSet is set to non-zero, then change it to -1. */
   ENTINF_SetValue(O, pwdLastSet, (LONGLONG)-1, prefixTable)
 endif
  
 /* Append objectSid to sIDHistory. */
 ENTINF_SetValue(O, sidHistory, 
     ENTINF_GetValue(O, sidHistory, prefixTable)
       + {ENTINF_GetValue(O, objectSid, prefixTable)})
  
 /* Compute the new proxiedObjectName value. */
 if ENTINF_GetValue(O, proxiedObjectName, prefixTable) ≠ null and
     GetProxyType(ENTINF_GetValue(O, proxiedObjectName)) = 
       PROXY_TYPE_MOVED_OBJECT then
   /* There's already a valid proxiedObjectName on the object, 
    * so just increment the epoch value. */
   proxyEpoch := GetProxyEpoch(ENTINF_GetValue(O, proxiedObjectName,
       prefixTable))+1
 else
   /* No valid proxiedObjectName, so start a new one. */
   proxyEpoch := 1
 endif
  
 /* Stamp the new proxiedObjectName value into ENTINF. */
 ENTINF_SetValue(O,
                 proxiedObjectName, 
                 MakeProxyValue(msgIn.pSrcNC^,
                                PROXY_TYPE_MOVED_OBJECT,
                                proxyEpoch),
                 prefixTable)
  
 if existingObj ≠ null then
   /* Purge existing object, it is about to be overwritten. */
   Expunge(existingObj)
 endif
  
 ImpersonateAuthorizationInfo(clientCreds)
 O.pName := msgIn.pDstName
 dwErr :=
     PerformAddOperation(
         O,
         msgOut^.V2.pAddedName^,
         AbstractPTFromConcretePT(msgIn.PrefixTable),
         TRUE)
  
 RevertToSelf()
 msgOut^.V2.win32error := dwErr
 return dwErr