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