4.1.2.3 Server Behavior of the IDL_DRSAddSidHistory Method

Informative summary of behavior: The IDL_DRSAddSidHistory method adds the SIDs associated with one principal (the source principal) to the sIDHistory attribute of another principal (the destination principal). The source principal's objectSid and any SIDs in the source principal's sIDHistory are added to the destination principal's sIDHistory. This method is called on a DC whose default NC contains the destination principal. If necessary, the destination DC will contact a DC whose default NC contains the source principal as part of executing this method.

This method has three different variants on this behavior, and the caller indicates which variant is desired by specifying a combination of flags in pmsgIn^.V1.flags.

  • If the DS_ADDSID_FLAG_PRIVATE_CHK_SECURE flag is specified, the first variant is selected. In this variant, the method verifies only that the RPC call is secure. It does not perform any further processing or manipulate the sIDHistory attribute of any object, regardless of other flags that might be present.

  • If DS_ADDSID_FLAG_PRIVATE_CHK_SECURE is not specified but DS_ADDSID_FLAG_PRIVATE_DEL_SRC_OBJ is specified, the second variant is selected. In this variant, the source and destination principals are in the same domain. The values of the objectSid and sIDHistory attributes of the source principal are added to the destination principal's sIDHistory attribute, and then the source principal is deleted. See [MS-ADTS] section 3.1.1.5.5 for more information about the delete operation. Loosely speaking, the destination principal adopts the source principal as an "alias" and the source principal disappears.

  • The third variant is selected by specifying neither DS_ADDSID_FLAG_PRIVATE_CHK_SECURE nor DS_ADDSID_FLAG_PRIVATE_DEL_SRC_OBJ. In this variant, the source and destination principals are in different forests. The values of the source principal's objectSid and sIDHistory attributes are copied into the destination principal's sIDHistory attribute, as in the second variant, but without deleting the source principal. Loosely speaking, the destination principal adopts the source principal as an "alias" while coexisting with the source principal.

The preceding are the only variants supported by the IDL_DRSAddSidHistory method. In particular, the case of source and destination principals in different domains within the same forest is not supported.

 ULONG
 IDL_DRSAddSidHistory(
    [in, ref] DRS_HANDLE hDrs,
    [in] DWORD dwInVersion,
    [in, ref, switch_is(dwInVersion)] DRS_MSG_ADDSIDREQ *pmsgIn,
    [out, ref] DWORD *pdwOutVersion,
    [out, ref, switch_is(*pdwOutVersion)] DRS_MSG_ADDSIDREPLY *pmsgOut)
  
 flags: DRS_ADDSID_FLAGS
 srcPrinc: DSName
 dstPrinc: DSName
 srcPrincInDst: DSName
 srcNc: DSName
 dstNc: DSName
 crSrc: DSName
 crDst: DSName
 partCtr: DSName
 srcDomainController: unicodestring
 srcCtx: ConnectionCtx
 srcPrincSid: SID
 srcPrincSidHistory: set of SID
 rt: ULONG
  
 ValidateDRSInput(hDrs, 20)
  
 pdwOutVersion^ := 1
 pmsgOut^.V1.dwWin32Error := ERROR_DS_INTERNAL_FAILURE
  
 flags := pmsgIn^.V1.flags
 if DS_ADDSID_FLAG_PRIVATE_CHK_SECURE in flags then
   /* First mode of operation: verify connection security. 
    * If connecting from off-machine, connection must have 128-bit 
    * encryption or better. */
   if (not IsLocalRpcCall(hDrs)) and
      (GetKeyLength(hDrs) < 128) then
     pmsgOut^.V1.dwWin32Error := ERROR_DS_MUST_RUN_ON_DST_DC
     return ERROR_DS_MUST_RUN_ON_DST_DC
   else
     return 0
   endif
 endif
  
 /* Currently, only version 1 is supported.  The RPC IDL definitions
  * for the interface do not allow passing in a version other than 1. */
 if dwInVersion ≠ 1 then
   return ERROR_INVALID_PARAMETER
 endif
  
 if DS_ADDSID_FLAG_PRIVATE_DEL_SRC_OBJ in flags then
   /* Second mode of operation: add objectSid/sidHistory from source 
    * principal to destination principal, then delete source 
    * principal. */
  
   /* Basic parameter validation */
   if (pmsgIn^.V1.SrcDomain ≠ null) or
      (pmsgIn^.V1.DstDomain ≠ null) or
      (pmsgIn^.V1.SrcCredsUserLength ≠ 0) or
      (pmsgIn^.V1.SrcCredsDomainLength ≠ 0) or
      (pmsgIn^.V1.SrcCredsPasswordLength ≠ 0) or
      (pmsgIn^.V1.SrcDomainController = "") or
      (pmsgIn^.V1.SrcPrincipal = null) or
      (pmsgIn^.V1.SrcPrincipal = "") or
      (pmsgIn^.V1.DstPrincipal = null) or
      (pmsgIn^.V1.DstPrincipal = "") then
     pmsgOut^.V1.dwWin32Error := ERROR_DS_INTERNAL_FAILURE
     return ERROR_INVALID_PARAMETER
   endif
  
   /* In this case, pmsgIn^.V1.SrcPrincipal and .DstPrincipal are
    * DNs. */
   srcPrinc := GetDSNameFromDN(pmsgIn^.V1.SrcPrincipal)
   dstPrinc := GetDSNameFromDN(pmsgIn^.V1.DstPrincipal)
   srcNc := GetObjectNC(srcPrinc)
   dstNc := GetObjectNC(dstPrinc)
  
   /* Source and destination principals must be in same domain. */
   if srcNc = null or dstNc = null or srcNc ≠ dstNc then
     pmsgOut^.V1.dwWin32Error := ERROR_INVALID_PARAMETER
     return 0
   endif
  
   /* Destination NC must be this server's default domain NC. */
   if dstNc ≠ DefaultNC() then
     pmsgOut^.V1.dwWin32Error := ERROR_DS_MASTERDSA_REQUIRED
     return 0
   endif
  
   /* Verify that this server has auditing enabled */
   if not IsAuditingEnabled () then
     pmsgOut^.V1.dwWin32Error := 
       ERROR_DS_DESTINATION_AUDITING_NOT_ENABLED
     return 0
   endif
  
   /* Must have the control access right. */
   if not AccessCheckCAR(dstNc, Migrate-SID-History) then
     GenerateFailureAudit()
     pmsgOut^.V1.dwWin32Error := ERROR_DS_INSUFF_ACCESS_RIGHTS
     return 0
   endif
  
   /* Destination domain must be in native mode. */
   partCtr := DescendantObject(ConfigNC(), "CN=Partitions,")
   if partCtr ≠ null
     crDst := select one dd from subtree partCtr where
       (crossRef in dd!objectClass and
        dd!nCName = dstNc)
   endif
   if partCtr = null or crDst = null then
     pmsgOut^.V1.dwWin32Error := ERROR_DS_INTERNAL_FAILURE
     return 0
   else
     if crDst!nTMixedDomain = 1 then
       pmsgOut^.V1.dwWin32Error := ERROR_DS_DST_DOMAIN_NOT_NATIVE
       return 0
     endif
   endif
  
   /* Validation of object state. */
   if (not ObjExists(srcPrinc)) or
      (not (user in srcPrinc!objectClass or 
            group in srcPrinc!objectClass)) or
      (not ObjExists(dstPrinc)) or
      (not (user in dstPrinc!objectClass or 
            group in dstPrinc!objectClass)) or
      (srcPrinc = dstPrinc) or
      (IsWellKnownDomainRelativeSid(srcPrinc!objectSid)) or
      (IsWellKnownDomainRelativeSid(dstPrinc!objectSid)) then
     pmsgOut^.V1.dwWin32Error := ERROR_INVALID_PARAMETER
     return 0
   endif
  
   /* Check that this machine has rights to delete the source principal. */
   if (not AccessCheckObject(srcPrinc, RIGHT_DELETE)) and
      (not AccessCheckObject(srcPrinc.parent, RIGHT_DS_DELETE_CHILD))
       then
     pmsgOut^.V1.dwWin32Error := ERROR_ACCESS_DENIED
     return 0
   endif
  
   /* Save the source principal's SID and SID history and then delete the principal */
   srcPrincSid := srcPrinc!objectSid
   srcPrincSidHistory := srcPrinc!sIDHistory
   rt = RemoveObj(srcPrinc,false)
   if(rt ≠ 0) then
     pmsgOut^.V1.dwWin32Error := rt
     return 0
   endif
  
   /* Add source principal's objectSid and sidHistory to 
    * destination principal's sidHistory. */
   dstPrinc!sidHistory := dstPrinc!sidHistory + {srcPrincSid}
   dstPrinc!sidHistory := dstPrinc!sidHistory + srcPrincSidHistory
  
   GenerateSuccessAudit()
   return 0
 endif
  
 /* Third mode of operation: add objectSid/sIDHistory from source 
  * principal to destination principal. Source principal is 
  * untouched. */
  
 /* Basic parameter validation. */
 if (pmsgIn^.V1.SrcDomain = null) or
    (pmsgIn^.V1.SrcDomain = "") or
    (pmsgIn^.V1.DstDomain = null) or
    (pmsgIn^.V1.DstDomain = "") or
    (pmsgIn^.V1.SrcCredsUserLength > 0 and
       pmsgIn^.V1.SrcCredsUser = null) or
    (pmsgIn^.V1.SrcCredsDomainLength > 0 and
       pmsgIn^.V1.SrcCredsDomain = null) or
    (pmsgIn^.V1.SrcCredsPasswordLength > 0 and
       pmsgIn^.V1.SrcCredsPassword = null) or
    (pmsgIn^.V1.SrcDomainController = "") or
    (pmsgIn^.V1.SrcPrincipal = null) or
    (pmsgIn^.V1.SrcPrincipal = "") or
    (pmsgIn^.V1.DstPrincipal = null) or
    (pmsgIn^.V1.DstPrincipal = "") then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_INTERNAL_FAILURE
   return ERROR_INVALID_PARAMETER
 endif
  
 /* Confirm destination domain is in forest of server. */
 crDst := select one dd from subtree ConfigNC() where
   (crossRef in dd!objectClass and
    (dd!dnsRoot = pmsgIn^.V1.DstDomain or
     dd!nETBIOSName = pmsgIn^.V1.DstDomain))
 if crDst = null then
   pmsgOut^.V1.dwWin32Error :=
     ERROR_DS_DESTINATION_DOMAIN_NOT_IN_FOREST
   return 0
 endif
  
 /* Confirm source domain is not in forest of server. */
 crSrc := select one ss from subtree ConfigNC() where
   (crossRef in ss!objectClass and
    (ss!dnsRoot = pmsgIn^.V1.SrcDomain or
     ss!nETBIOSName = pmsgIn^.V1.SrcDomain)
     and FLAG_CR_NTDS_NC in ss!systemFlags
     and FLAG_CR_NTDS_DOMAIN in ss!systemFlags)
 if crSrc ≠ null then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_SOURCE_DOMAIN_IN_FOREST
   return 0
 endif
  
 /* Destination NC must be this server's default domain NC. */
 if crDst!nCName ≠ DefaultNC() then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_MASTERDSA_REQUIRED
   return 0
 endif
  
 /* Destination domain must be in native mode. */
 if crDst!nTMixedDomain = 1 then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_DST_DOMAIN_NOT_NATIVE
   return 0
 endif
  
 dstNC := crDst!nCName
  
 /* Verify this server has auditing enabled for destination domain. */
 if not IsAuditingEnabled () then
   pmsgOut^.V1.dwWin32Error := 
     ERROR_DS_DESTINATION_AUDITING_NOT_ENABLED
   return 0
 endif
  
 /* Must have the control access right. */
 if not AccessCheckCAR(dstNc, Migrate-SID-History) then
   GenerateFailureAudit()
   pmsgOut^.V1.dwWin32Error := ERROR_DS_INSUFF_ACCESS_RIGHTS
   return 0
 endif
  
 /* Retrieve destination principal.
  * In this case, pmsgIn^.V1.DstPrincipal is a SAM name. */
 dstPrinc := select one o from subtree DefaultNC() where
     (o!sAMAccountName = pmsgIn^.V1.DstPrincipal)
 if dstPrinc = null then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_OBJ_NOT_FOUND
   return 0
 endif
  
 /* Locate a source DC if one wasn't supplied. Source DC must be
  * the PDC FSMO role owner. */
 srcDomainController := pMsgin^.V1.SrcDomainController
 if srcDomainController = null then
   srcDomainController := GetPDC(pmsgIn^.V1.SrcDomain)
 else
   if srcDomainController ≠ GetPDC(pmsgIn^.V1.SrcDomain) then
     pmsgOut^.V1.dwWin32Error := ERROR_INVALID_DOMAIN_ROLE
     return 0
   endif
 endif
 if srcDomainController = null then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_CANT_FIND_DC_FOR_SRC_DOMAIN
   return 0
 endif
  
 /* Connect to source DC, using supplied credentials if applicable. */
 if (pmsgIn^.V1.SrcCredsUserLength = 0) and
    (pmsgIn^.V1.SrcCredsPasswordLength = 0) and
    (pmsgIn^.V1.SrcCredsDomainLength = 0) then
   srcCtx := ConnectToDC(srcDomainController)
 else
   srcCtx := ConnectToDCWithCreds(srcDomainController,
       pmsgIn^.V1.SrcCredsUser, pmsgIn^.V1.SrcCredsPassword,
       pmsgIn^.V1.SrcCredsDomain)
 endif
  
 if (srcCtx = null) then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_CANT_FIND_DC_FOR_SRC_DOMAIN
   return 0
 endif
  
 /* Confirm client has administrative rights on source DC. */
 if not HasAdminRights(srcCtx) then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_INSUFF_ACCESS_RIGHTS
   return 0
 endif
  
 /* Retrieve source principal from source DC using the remote connection.
  * In this case, pmsgIn^.V1.SrcPrincipal is a SAM name.
  * Example: If pmsgIn^.V1.SrcPrincipal value is username1 then 
  * following query is executed in the source DC:
  *    select one o from subtree dc.defaultNC where (o!sAMAccountName = "username1")
  */
 srcPrinc := RemoteQuery(srcCtx, 
       "select one o from subtree dc.defaultNC where (o!sAMAccountName = " 
       + '"' + pmsgIn^.V1.SrcPrincipal + '"' + ")"
       )
 if srcPrinc = null then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_OBJ_NOT_FOUND
   return 0
 endif
  
 /* Source principal must be user (which includes computer) or
  * group.*/
 if not (group in srcPrinc!objectClass or
         user in srcPrinc!objectClass) then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_SRC_OBJ_NOT_GROUP_OR_USER
   return 0
 endif
  
 srcPrincSid := srcPrinc!objectSid
 srcPrincSidHistory := srcPrinc!sIDHistory
  
 /* Verify that no principal other than the destination 
 * principal exists in the destination forest that contains 
 * a SID that matches the source principal. */
 if IsGC() or IsAdlds() then
   srcPrincInDst := select one o from subtree DefaultNC() where 
   (o ≠ dstPrinc) and 
   ((o!objectSid = srcPrincSid) or 
   (o!objectSid in srcPrincSidHistory) or 
   (srcPrincSid in o!sIDHistory)) or 
   ((srcPrincSidHistory ∩ o!sIDHistory) ≠ {}))
  
   if srcPrincInDst ≠ null then 
     pmsgOut^.V1.dwWin32Error := ERROR_DS_SRC_SID_EXISTS_IN_FOREST 
     return 0 
   endif
  
 else
   /* The current DC is not a GC server.
   * We need to locate a GC server and perform an IDL_DRSCrackNames query against it in order 
   for the SID search to be Forest scoped */
   gcDomainController : unicodestring
   hDrsGc: DRS_HANDLE
   crackMsgIn: DRS_MSG_CRACKREQ_V1
   crackOut: DS_NAME_RESULTW
  
   gcDomainController := FindGC()
  
   if gcDomainController = null then 
     return STATUS_DS_GC_NOT_AVAILABLE 
   endif
  
   /* Bind to GC */   
   hDrsGc := BindToDSA(gcDomainController)
   if hDrsGc = null then
     pmsgOut^.V1.dwWin32Error := STATUS_DS_GC_NOT_AVAILABLE
     return 0 
   endif
  
   crackMsgIn.dwFlags := DS_NAME_FLAG_GCVERIFY
   crackMsgIn.formatOffered := DS_STRING_SID_NAME
   crackMsgIn.formatDesired := DS_UNIQUE_ID_NAME
   crackMsgIn.cNames := 1
   crackMsgIn.rpNames[0] := srcPrincSid
  
   crackNamesErr := IDL_DRSCrackNames(
     hDrsGc,
     dwInVersion,
     crackMsgIn,
     pdwOutVersion,
     ADR(crackOut))
  
   if crackNamesErr ≠ 0 then 
     if crackNamesErr = DS_NAME_ERROR_NOT_UNIQUE then
       pmsgOut^.V1.dwWin32Error := ERROR_DS_SRC_SID_EXISTS_IN_FOREST
       return 0
     elseif crackNamesErr ≠ DS_NAME_ERROR_NOT_FOUND and
     crackNamesErr ≠ DS_NAME_ERROR_DOMAIN_ONLY then
       pmsgOut^.V1.dwWin32Error := ERROR_DS_INTERNAL_FAILURE
       return 0
   endif      
  
   if crackOut.rItems ≠ null and
   crackOut.rItems[0].pName ≠ dstPrinc!objectGUID then
     pmsgOut^.V1.dwWin32Error := ERROR_DS_SRC_SID_EXISTS_IN_FOREST 
     return 0
   endif
  
   UnbindFromDSA(hDrsGc)
  
 endif
  
 /* Confirm source domain has auditing enabled and generate an audit
  * event on it. */
 if not GenerateSuccessAuditRemotely(srcCtx)
   pmsgOut^.V1.dwWin32Error := ERROR_DS_SOURCE_AUDITING_NOT_ENABLED
   return 0
 endif
  
 /* Verify that if source domain is running Windows NT 4.0, it is
  * running at least Service Pack 4 of that operating system. */
 if not IsNT4SP4OrBetter(srcCtx)
   pmsgOut^.V1.dwWin32Error := ERROR_DS_SRC_DC_MUST_BE_SP4_OR_GREATER
   return 0
 endif
  
 /* Verify that if source domain has a domain local group srcDomainNetBIOSName$$$
 */
 if IsAuditingGroupPresent(srcDomainController, pmsgIn^.V1.SrcDomain) = ERROR_NO_SUCH_ALIAS
   pmsgOut^.V1.dwWin32Error := ERROR_NO_SUCH_ALIAS
   return 0
 endif
  
  
 /* Source and destination principals must both be computer, or both
  * be user, or both be group. The order is important: although
  * computer objects are user objects, the case is disallowed where
  * one principal is a computer and the other principal is a user
  * but not a computer. */
 if ((computer in srcPrinc!objectClass and
       not computer in dstPrinc!objectClass) or
     (computer in dstPrinc!objectClass and
       not computer in srcPrinc!objectClass)) or
    ((user in srcPrinc!objectClass and
       not user in dstPrinc!objectClass) or
     (user in dstPrinc!objectClass and
       not user in srcPrinc!objectClass)) or
    ((group in srcPrinc!objectClass and
       not group in dstPrinc!objectClass) or
     (group in dstPrinc!objectClass and
       not group in srcPrinc!objectClass)) then
   pmsgOut^.V1.dwWin32Error := 
       ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH
   return 0
 endif
  
 /* Class-specific object state tests. 
  * Note that computer is a subclass of user, so the following test 
  * applies to both user and computer objects. */
 if user in srcPrinc!objectClass then
   if srcPrinc!userAccountControl ∩ {ADS_UF_NORMAL_ACCOUNT,
                                     ADS_UF_WORKSTATION_TRUST_ACCOUNT,
                                     ADS_UF_SERVER_TRUST_ACCOUNT} ≠
      dstPrinc!userAccountControl ∩ {ADS_UF_NORMAL_ACCOUNT,
                                     ADS_UF_WORKSTATION_TRUST_ACCOUNT,
                                     ADS_UF_SERVER_TRUST_ACCOUNT} then
     pmsgOut^.V1.dwWin32Error := 
         ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH
     return 0
 endif
  
 if group in srcPrinc!objectClass and 
     srcPrinc!groupType ≠ dstPrinc!groupType then
   pmsgOut^.V1.dwWin32Error := 
       ERROR_DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH
   return 0
 endif
  
 /* Check if source principal is built-in principal. */
 if IsBuiltinPrincipal(srcPrinc!objectSid) then
   pmsgOut^.V1.dwWin32Error := ERROR_DS_UNWILLING_TO_PERFORM
   return 0
 endif
  
 /* If source principal has well-known domain-relative SID 
  * make sure final RIDs of source and destination principals 
  * are the same. */
 if IsWellKnownDomainRelativeSid(srcPrinc!objectSid) then
   if LastRID(srcPrinc!objectSid) ≠ LastRID(dstPrinc!objectSid)
     pmsgOut^.V1.dwWin32Error := ERROR_DS_UNWILLING_TO_PERFORM
     return 0
   endif
 endif
  
 /* Add source principal's objectSid and sIDHistory to
  * destination principal's sidHistory. */
 dstPrinc!sIDHistory := dstPrinc!sIDHistory + {srcPrincSid}
 dstPrinc!sIDHistory := dstPrinc!sIDHistory + srcPrincSidHistory
 GenerateSuccessAudit()
 return 0