Export (0) Print
Expand All

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 sIDHistoryattribute 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 may 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
  srcCxt := 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
 
Show:
© 2014 Microsoft