Discussion:
Searching Trusted Domain Foreign Security Principals
(too old to reply)
d***@umich.edu
2006-08-29 17:47:07 UTC
Permalink
Help!

Here's the situation: I am creating a VBScript login script that
provides network resources based on the user's group membership. I'm
working with two domains, both with a trust between them. I've done
enough research to know that the TokenGroups property does not get
populated with groups in the other trusted domain.

So, for a user that logs into one domain from the other, I know that
the 1st step is to build a list of the user's local group memberships.
I know how to get the primary group object based on the PrimaryGroupID
property, and I've created a WalkGroups sub to recursively build a list
of local groups that the user is a member of.

That part that I'm confused about is how to link the user's local SIDs
to the Trusted Domain's Foreign Security Principal objects. I have a
function to return a hex formated string of an Object's SID and a
function to return the SDDL sid. I'm assuming I have to pull up a
collection of the FSP objects in the Trusted Domain, and then loop
through to see if the FSP's sid matching any of the user's SIDs? I was
hoping that I could just search AD through an ADODB connection for all
FSP's where the Object's SID is in a range of sids from my initial
compilation.

What am I missing? What is the best practice way to link up SIDs to
Trusted Domain FSPs? Once I get the FSPs that pertain to a user, I can
just recursively run through the Objects MemberOf property and go from
there.

Thank You,
-Dave Fribley
d***@umich.edu
2006-08-29 18:25:25 UTC
Permalink
I think what I was missing was having the Domain Controller name in my
LDAP query.

If I use:

<LDAP://DCName/CN=ForeignSecurityPrincipals,DC=domain,DC=com>

it appears that I get back what I need. Before I was not using the
Controller name in my query so I assume the queries were again the
source domain's controller. Whoops! Is this the best practice way to
link Foreign Security Principals?

Thanks!!!!!
-Dave
Post by d***@umich.edu
What am I missing? What is the best practice way to link up SIDs to
Trusted Domain FSPs? Once I get the FSPs that pertain to a user, I can
just recursively run through the Objects MemberOf property and go from
there.
Thank You,
-Dave Fribley
Joe Kaplan
2006-08-29 20:21:15 UTC
Permalink
Did you try using IADsNameTranslate in order to convert them into friendly
names?

Joe K.
--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
Post by d***@umich.edu
I think what I was missing was having the Domain Controller name in my
LDAP query.
<LDAP://DCName/CN=ForeignSecurityPrincipals,DC=domain,DC=com>
it appears that I get back what I need. Before I was not using the
Controller name in my query so I assume the queries were again the
source domain's controller. Whoops! Is this the best practice way to
link Foreign Security Principals?
Thanks!!!!!
-Dave
Post by d***@umich.edu
What am I missing? What is the best practice way to link up SIDs to
Trusted Domain FSPs? Once I get the FSPs that pertain to a user, I can
just recursively run through the Objects MemberOf property and go from
there.
Thank You,
-Dave Fribley
d***@umich.edu
2006-08-31 13:45:40 UTC
Permalink
I have not tried IADsNameTranslate before your post. Pretty quick into
experimenting with a Name Translate object, I noticed that I get an
error when trying to translate to a SID string. I've seen a few other
posts in these groups where others have the same problem. That's fine
since I start with SIDs anyway from the TokenGroups property.

So for users that are logging into the local domain, I've change the
way I get group names:

Old Way:
---------------------------------------------------------------------------
' Retrieve tokenGroups array, a calculated attribute.
user.GetInfoEx ARRAY("TokenGroups"),0
gsids = user.Get("TokenGroups")

' Start a string of SIDS to construct an ADO filter
strFilter = "(|"
For Each sid In gsids
strFilter = strFilter & "(ObjectSid=" & OctetToEscapedHexStr(sid) &
")"
Next
strFilter = strFilter & ")"

' Start an ADO connection to pull Group Names from AD
Set cmd = CreateObject("ADODB.Command")
Set conn = CreateObject("ADODB.Connection")

conn.Provider = "ADsDSOObject"
conn.Open "Active Directory Provider"

cmd.ActiveConnection = conn
cmd.CommandText = "<LDAP://DC=Domain,DC=com>;(&(ObjectClass=group)" &
_
strFilter & ");sAMAccountName;subtree"

' Get the group names from AD
Set rs = cmd.Execute
While Not rs.EOF
grps.Add LCase(rs.Fields("SAMAccountName").value), "-"
rs.MoveNext
Wend

' Close connections
rs.Close
conn.Close
---------------------------------------------------------------------------

New
---------------------------------------------------------------------------
' Retrieve tokenGroups array, a calculated attribute.
user.GetInfoEx ARRAY("TokenGroups"),0
gsids = user.Get("TokenGroups")

' Use Name Translate Object The easy way
Set nto = CreateObject("NameTranslate")
nto.Init ADS_NAME_INITTYPE_DOMAIN, "MYDOMAIN"

' Loop through each binary SID, convert it to a string and
' get the object's display name
For Each sid In gsids
' Set the Name Translate Object's source
nto.Set ADS_NAME_TYPE_SSDL, ObjSidToStrSid(sid)
' Get the Display Name of the Object
nt4name = nto.Get(ADS_NAME_TYPE_NT4)
grps.Add LCase(MID(nt4name, InStrRev(nt4name, "\") + 1)), "-"
Next
---------------------------------------------------------------------------

As you can see, that has cleaned up my code significantly. I'm not
sure of performance, but I'm not noticing any serious changes in
response times. You'll note I used the NT4 display name because I
would get an error when trying to go from SID to Display name.

The IADsNameTranslate works great for local users, but users across a
Trusted Domain need a little more functionality. Since the TokenGroups
property doesn't populate across Trusted Domains, I first start out by
rounding up all SIDs associate with the user.

-User sid
-Walk Primary Group sid
-Walk MemberOf property sids

Once I have all of those, I don't know which ones will have an
associated Foreign Security Principal in my Trusted Domain. So, after
I compile all of the user's SIDs, I then query LDAP through an ADODB
connection:

---------------------------------------------------------------------------
strFilter = "(|"
For Each hsid In sids.Keys
strFilter = strFilter & "(ObjectSid=" & hsid & ")"
Next
strFilter = strFilter & ")"

' Start an ADO connection to pull Group Names from AD
Set cmd = CreateObject("ADODB.Command")
Set conn = CreateObject("ADODB.Connection")

conn.Provider = "ADsDSOObject"
conn.Open "Active Directory Provider"

cmd.ActiveConnection = conn
cmd.CommandText =
"<LDAP://DC1/CN=ForeignSecurityPrincipals,DC=Domain,DC=com>;(&(objectClass=foreignSecurityPrincipal)"
& strFilter & ");distinguishedName;subtree"
---------------------------------------------------------------------------

After I collect all associated FSPs, I then have to recursively Walk
the MemberOf property of each FSP object, and so forth, and build a
list of group names.

I know that the MemberOf collection contains DNs and I could Name
Translate them into nice names, but I still need to get the Object to
determine if it's a group object and Walk its MemberOf property. Since
I already have the Object, I just call the obj.Get("Name") method
instead of doing a Name Translate lookup.

---------------------------------------------------------------------------
Sub WalkGroups(adStr, dc, col)
' Get the AD Object
Dim oGrp

If IsEmpty(dc) Then
Set oGrp = GetObject("LDAP://" & adStr)
Else
Set oGrp = GetObject("LDAP://" & dc & "/" & adStr)
End IF

' Only Continue if this object is a group
If LCase(oGrp.Class) <> "group" Then
Exit Sub
End If

' Found one!
If Not IsEmpty(col) Then
col.Add OctetToHexStr(oGrp.ObjectSID, True), "-"
Else
' Add the Group Name
grps.Add LCase(oGrp.Get("Name")), "-"
End If

' Get this group's member of records
gmems = oGrp.MemberOf
' No 'Member of' records - done
If IsEmpty(gmems) Then
Exit Sub
End If
' Only one 'Member of' record
If TypeName(gmems) = "String" Then
WalkGroups gmems, dc, col
Else
' We have an array of 'Member of' records
For Each path In gmems
WalkGroups path, dc, col
Next
End If
End Sub
---------------------------------------------------------------------------

Long story short, Thanks for the reply! I did use a Name Translate for
local users, but I had to leave Trusted Domain users the way they are.
So far, everything appears to work as expected. Is this how you would
go about getting a list of group memberships in Trusted Domains? PS:
I'm only working with 1 Trusted Domain - That's why the code only
queries one other Trusted Domain.

Thanks for the help!
-Dave Fribley
Post by Joe Kaplan
Did you try using IADsNameTranslate in order to convert them into friendly
names?
Joe K.
--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
Joe Kaplan
2006-08-31 15:26:06 UTC
Permalink
I'm pretty sure that when translating SIDs, IADsNameTranslate expects them
in SDDL form (S-1-5-20-xxxx), not octet or binary. I don't know of an easy
way to do that conversion in vbscript (which is one of the many reasons I
don't program in it), but maybe there is something out there that does that.

It should work ok to convert groups based on their binary SID though once
you pass in the value it expects.

I hear you regarding the issue with tokenGroups not pulling in groups from a
trusted domain. It is supposed to provide the groups in the token you would
get if you logged into the domain you make the tokenGroups call against.
The recursive approach is probably the only way to got to get "everything".

Best of luck!

Joe K.
--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
Post by d***@umich.edu
I have not tried IADsNameTranslate before your post. Pretty quick into
experimenting with a Name Translate object, I noticed that I get an
error when trying to translate to a SID string. I've seen a few other
posts in these groups where others have the same problem. That's fine
since I start with SIDs anyway from the TokenGroups property.
So for users that are logging into the local domain, I've change the
---------------------------------------------------------------------------
' Retrieve tokenGroups array, a calculated attribute.
user.GetInfoEx ARRAY("TokenGroups"),0
gsids = user.Get("TokenGroups")
' Start a string of SIDS to construct an ADO filter
strFilter = "(|"
For Each sid In gsids
strFilter = strFilter & "(ObjectSid=" & OctetToEscapedHexStr(sid) &
")"
Next
strFilter = strFilter & ")"
' Start an ADO connection to pull Group Names from AD
Set cmd = CreateObject("ADODB.Command")
Set conn = CreateObject("ADODB.Connection")
conn.Provider = "ADsDSOObject"
conn.Open "Active Directory Provider"
cmd.ActiveConnection = conn
cmd.CommandText = "<LDAP://DC=Domain,DC=com>;(&(ObjectClass=group)" &
_
strFilter & ");sAMAccountName;subtree"
' Get the group names from AD
Set rs = cmd.Execute
While Not rs.EOF
grps.Add LCase(rs.Fields("SAMAccountName").value), "-"
rs.MoveNext
Wend
' Close connections
rs.Close
conn.Close
---------------------------------------------------------------------------
New
---------------------------------------------------------------------------
' Retrieve tokenGroups array, a calculated attribute.
user.GetInfoEx ARRAY("TokenGroups"),0
gsids = user.Get("TokenGroups")
' Use Name Translate Object The easy way
Set nto = CreateObject("NameTranslate")
nto.Init ADS_NAME_INITTYPE_DOMAIN, "MYDOMAIN"
' Loop through each binary SID, convert it to a string and
' get the object's display name
For Each sid In gsids
' Set the Name Translate Object's source
nto.Set ADS_NAME_TYPE_SSDL, ObjSidToStrSid(sid)
' Get the Display Name of the Object
nt4name = nto.Get(ADS_NAME_TYPE_NT4)
grps.Add LCase(MID(nt4name, InStrRev(nt4name, "\") + 1)), "-"
Next
---------------------------------------------------------------------------
As you can see, that has cleaned up my code significantly. I'm not
sure of performance, but I'm not noticing any serious changes in
response times. You'll note I used the NT4 display name because I
would get an error when trying to go from SID to Display name.
The IADsNameTranslate works great for local users, but users across a
Trusted Domain need a little more functionality. Since the TokenGroups
property doesn't populate across Trusted Domains, I first start out by
rounding up all SIDs associate with the user.
-User sid
-Walk Primary Group sid
-Walk MemberOf property sids
Once I have all of those, I don't know which ones will have an
associated Foreign Security Principal in my Trusted Domain. So, after
I compile all of the user's SIDs, I then query LDAP through an ADODB
---------------------------------------------------------------------------
strFilter = "(|"
For Each hsid In sids.Keys
strFilter = strFilter & "(ObjectSid=" & hsid & ")"
Next
strFilter = strFilter & ")"
' Start an ADO connection to pull Group Names from AD
Set cmd = CreateObject("ADODB.Command")
Set conn = CreateObject("ADODB.Connection")
conn.Provider = "ADsDSOObject"
conn.Open "Active Directory Provider"
cmd.ActiveConnection = conn
cmd.CommandText =
"<LDAP://DC1/CN=ForeignSecurityPrincipals,DC=Domain,DC=com>;(&(objectClass=foreignSecurityPrincipal)"
& strFilter & ");distinguishedName;subtree"
---------------------------------------------------------------------------
After I collect all associated FSPs, I then have to recursively Walk
the MemberOf property of each FSP object, and so forth, and build a
list of group names.
I know that the MemberOf collection contains DNs and I could Name
Translate them into nice names, but I still need to get the Object to
determine if it's a group object and Walk its MemberOf property. Since
I already have the Object, I just call the obj.Get("Name") method
instead of doing a Name Translate lookup.
---------------------------------------------------------------------------
Sub WalkGroups(adStr, dc, col)
' Get the AD Object
Dim oGrp
If IsEmpty(dc) Then
Set oGrp = GetObject("LDAP://" & adStr)
Else
Set oGrp = GetObject("LDAP://" & dc & "/" & adStr)
End IF
' Only Continue if this object is a group
If LCase(oGrp.Class) <> "group" Then
Exit Sub
End If
' Found one!
If Not IsEmpty(col) Then
col.Add OctetToHexStr(oGrp.ObjectSID, True), "-"
Else
' Add the Group Name
grps.Add LCase(oGrp.Get("Name")), "-"
End If
' Get this group's member of records
gmems = oGrp.MemberOf
' No 'Member of' records - done
If IsEmpty(gmems) Then
Exit Sub
End If
' Only one 'Member of' record
If TypeName(gmems) = "String" Then
WalkGroups gmems, dc, col
Else
' We have an array of 'Member of' records
For Each path In gmems
WalkGroups path, dc, col
Next
End If
End Sub
---------------------------------------------------------------------------
Long story short, Thanks for the reply! I did use a Name Translate for
local users, but I had to leave Trusted Domain users the way they are.
So far, everything appears to work as expected. Is this how you would
I'm only working with 1 Trusted Domain - That's why the code only
queries one other Trusted Domain.
Thanks for the help!
-Dave Fribley
Post by Joe Kaplan
Did you try using IADsNameTranslate in order to convert them into friendly
names?
Joe K.
--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
d***@umich.edu
2006-08-31 16:27:42 UTC
Permalink
I put together this example script to demonstrate the error I was
getting. When I went to run my example script it ran perfectly, and I
wasn't sure why it didn't before. Then I remembered that the error I
was getting occured when Translating from SID to ADS_NAME_TYPE_DISPLAY.
As a work around, I just translated the group SID to ADS_NAME_TYPE_NT4
and stripped the name off of the end of that string. At any rate, I
feel pretty good about my current methods base on our thread. No need
to reply to this example, this is just FYI. Here is my example that
will demonstrate the error I'm getting:

-----------------------------------------------------------------------------------------------------
'Description: Name Translate Example

Const ADS_NAME_INITTYPE_DOMAIN = 1
Const ADS_NAME_INITTYPE_GC = 3

Const ADS_NAME_TYPE_SSDL = 12
Const ADS_NAME_TYPE_1779 = 1
Const ADS_NAME_TYPE_DISPLAY = 4

' Create user object to get group information
Set user = GetObject("LDAP://" & CreateObject("ADSystemInfo").UserName)

' Use Name Translate Object - The easy way
Set nto = CreateObject("NameTranslate")
nto.Init ADS_NAME_INITTYPE_GC, ""

' Compile the SID of my Primary Group
usid = ObjSidToStrSid(user.ObjectSID)
pgsid = Left(usid, InStrRev(usid,"-")) & user.PrimaryGroupID

MsgBox("Primary Group SID: " & pgsid)

' Set the Name Translate Object's source
nto.Set ADS_NAME_TYPE_SSDL, pgsid
' Get the DN of the Object
MsgBox("Primary Group DN: " & nto.Get(ADS_NAME_TYPE_1779))
' Get the Display Name of Object
MsgBox("Primary Group Display Name: " & nto.Get(ADS_NAME_TYPE_DISPLAY))

Function ObjSidToStrSid(arrSid)
' Function to convert OctetString (byte array) to Decimal string
(SDDL) Sid.
Dim strHex, strDec

strHex = OctetToHexStr(arrSid)
strDec = HexStrToDecStr(strHex)
ObjSidToStrSid = strDec
End Function ' ObjSidToStrSid

Function OctetToHexStr(bOctet)
Dim k
OctetToHexStr = ""
For k = 1 To LenB(bOctet)
OctetToHexStr = OctetToHexStr & _
Right("0" & Hex(Ascb(Midb(bOctet, k, 1))), 2)
Next
End Function

Function HexStrToDecStr(strSid)
' Function to convert Hex string Sid to Decimal string (SDDL) Sid.
' SID anatomy:
' Byte Position
' 0 : SID Structure Revision Level (SRL)
' 1 : Number of Subauthority/Relative Identifier
' 2-7 : Identifier Authority Value (IAV) [48 bits]
' 8-x : Variable number of Subauthority or Relative Identifier
(RID) [32 bits]
'
' Example:
'
' <Domain/Machine>\Administrator
' Pos : 0 | 1 | 2 3 4 5 6 7 | 8 9 10 11 | 12 13 14 15
| 16 17 18 19 | 20 21 22 23 | 24 25 26 27
' Value: 01 | 05 | 00 00 00 00 00 05 | 15 00 00 00 | 06 4E 7D 7F
| 11 57 56 7A | 04 11 C5 20 | F4 01 00 00
' str : S- 1 | | -5 | -21 | -2138918406
| -2052478737 | -549785860 | -500

Const BYTES_IN_32BITS = 4
Const SRL_BYTE = 0
Const IAV_START_BYTE = 2
Const IAV_END_BYTE = 7
Const RID_START_BYTE = 8
Const MSB = 3 'Most significant byte
Const LSB = 0 'Least significant byte

Dim arrbytSid, lngTemp, base, offset, i
ReDim arrbytSid(Len(strSid)/2 - 1)

' Convert hex string into integer array
For i = 0 To UBound(arrbytSid)
arrbytSid(i) = CInt("&H" & Mid(strSid, 2 * i + 1, 2))
Next

' Add SRL number
HexStrToDecStr = "S-" & arrbytSid(SRL_BYTE)

' Add Identifier Authority Value
lngTemp = 0
For i = IAV_START_BYTE To IAV_END_BYTE
lngTemp = lngTemp * 256 + arrbytSid(i)
Next
HexStrToDecStr = HexStrToDecStr & "-" & CStr(lngTemp)

' Add a variable number of 32-bit subauthority or
' relative identifier (RID) values.
' Bytes are in reverse significant order.
' i.e. HEX 01 02 03 04 => HEX 04 03 02 01
' = (((0 * 256 + 04) * 256 + 03) * 256 + 02) * 256 + 01
' = DEC 67305985
For base = RID_START_BYTE To UBound(arrbytSid) Step BYTES_IN_32BITS

lngTemp = 0
For offset = MSB to LSB Step -1
lngTemp = lngTemp * 256 + arrbytSid(base + offset)
Next
HexStrToDecStr = HexStrToDecStr & "-" & CStr(lngTemp)
Next
End Function ' HexStrToDecStr
-----------------------------------------------------------------------------------------------------

A little bit ago I found this HexStrToDecStr function in these forums,
written by Richard Mueller.

Thanks for the help Joe - Much Appreciated!
-Dave Fribley
Post by Joe Kaplan
I'm pretty sure that when translating SIDs, IADsNameTranslate expects them
in SDDL form (S-1-5-20-xxxx), not octet or binary. I don't know of an easy
way to do that conversion in vbscript (which is one of the many reasons I
don't program in it), but maybe there is something out there that does that.
It should work ok to convert groups based on their binary SID though once
you pass in the value it expects.
Loading...