Strings vs. byte[]

G

Guest

Using .NET I did check some of the properties in AD and I see that for
some reasons there is a difference in the type of the result value.
For example, SearchResult.Properties["operatingsystem"][0] returns a
value of type string on my development box under WinXP. The same query
on a server under Win2003 returns a value of type byte[] (bytes
array). Other "string" properties like name, distinguishedName, etc.
return always a string value.

What is the reason of this difference?

Thank you.
 
R

Richard Mueller [MVP]

Alexey said:
Using .NET I did check some of the properties in AD and I see that for
some reasons there is a difference in the type of the result value.
For example, SearchResult.Properties["operatingsystem"][0] returns a
value of type string on my development box under WinXP. The same query
on a server under Win2003 returns a value of type byte[] (bytes
array). Other "string" properties like name, distinguishedName, etc.
return always a string value.

What is the reason of this difference?

I don't see a difference between the operatingSystem attribute and other
DirectoryString attributes, like cn, sn, or givenName. The distinguishedName
attribute is datatype DN. The Name attribute (RDN) is really a property
method (it returns the Relative Distinguished Name of the object). Some
attributes are datatype OctetString, which is a byte array. Examples are
objectGuid and logonHours. Others are datatype SID, which is also a byte
array, such as objectSid and tokenGroups. You can see this using a tool like
ADSI Edit. I cannot see any way operatingSystem would be interpreted as a
byte array.
 
J

Joe Kaplan

It is probably an issue with the schema not getting mapped correctly which
is usually a security problem in AD and ADAM. I wrote a lengthy sidebar in
my book on this issue in ch 6.

ADSI attempts to read the abstract schema of the directory by binding to
rootDSE, finding the subschemaSubentry DN value and then attempting to
search that object to read the abstract schema and populate the schema
cache. If for some reason the code cannot read the subschemaSubentry object
(which is usually due to binding as the anonymous user), the schema will not
be mapped a default schema will be used. Whenever the DirectorySearcher
encounters an attribute that has no mapped schema data type, it returns it
as a byte[]. You can convert this to a string using the UTF8 encoding class
if you need to. The DirectoryEntry will return an error for the same
attribute, as it uses a different marshaling layer than the
DirectorySearcher and it doesn't default to byte[] for types that can't be
mapped.

I see this problem frequently in web applications where default credentials
are used with the DirectoryEntry, but the current security context is either
not a domain account or the application is not configured properly for
delegation. Checking the value of
System.Security.Principal.WindowsIdentity.GetCurrent().Name will reveal the
current identity and will assist in troubleshooting.

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
--
Richard Mueller said:
Alexey said:
Using .NET I did check some of the properties in AD and I see that for
some reasons there is a difference in the type of the result value.
For example, SearchResult.Properties["operatingsystem"][0] returns a
value of type string on my development box under WinXP. The same query
on a server under Win2003 returns a value of type byte[] (bytes
array). Other "string" properties like name, distinguishedName, etc.
return always a string value.

What is the reason of this difference?

I don't see a difference between the operatingSystem attribute and other
DirectoryString attributes, like cn, sn, or givenName. The
distinguishedName attribute is datatype DN. The Name attribute (RDN) is
really a property method (it returns the Relative Distinguished Name of
the object). Some attributes are datatype OctetString, which is a byte
array. Examples are objectGuid and logonHours. Others are datatype SID,
which is also a byte array, such as objectSid and tokenGroups. You can see
this using a tool like ADSI Edit. I cannot see any way operatingSystem
would be interpreted as a byte array.
 
G

Guest

It is probably an issue with the schema not getting mapped correctly which
is usually a security problem in AD and ADAM.  I wrote a lengthy sidebarin
my book on this issue in ch 6.

ADSI attempts to read the abstract schema of the directory by binding to
rootDSE, finding the subschemaSubentry DN value and then attempting to
search that object to read the abstract schema and populate the schema
cache.  If for some reason the code cannot read the subschemaSubentry object
(which is usually due to binding as the anonymous user), the schema will not
be mapped a default schema will be used.  Whenever the DirectorySearcher
encounters an attribute that has no mapped schema data type, it returns it
as a byte[].  You can convert this to a string using the UTF8 encoding class
if you need to.  The DirectoryEntry will return an error for the same
attribute, as it uses a different marshaling layer than the
DirectorySearcher and it doesn't default to byte[] for types that can't be
mapped.

I see this problem frequently in web applications where default credentials
are used with the DirectoryEntry, but the current security context is either
not a domain account or the application is not configured properly for
delegation.  Checking the value of
System.Security.Principal.WindowsIdentity.GetCurrent().Name will reveal the
current identity and will assist in troubleshooting.

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
--
message

Anon User wrote:
Using .NET I did check some of the properties in AD and I see that for
some reasons there is a difference in the type of the result value.
For example, SearchResult.Properties["operatingsystem"][0] returns a
value of type string on my development box under WinXP. The same query
on a server under Win2003 returns a value of type byte[] (bytes
array). Other "string" properties like name, distinguishedName, etc.
return always a string value.
What is the reason of this difference?
I don't see a difference between the operatingSystem attribute and other
DirectoryString attributes, like cn, sn, or givenName. The
distinguishedName attribute is datatype DN. The Name attribute (RDN) is
really a property method (it returns the Relative Distinguished Name of
the object). Some attributes are datatype OctetString, which is a byte
array. Examples are objectGuid and logonHours. Others are datatype SID,
which is also a byte array, such as objectSid and tokenGroups. You can see
this using a tool like ADSI Edit. I cannot see any way operatingSystem
would be interpreted as a byte array.
--
Richard Mueller
Microsoft MVP Scripting and ADSI
Hilltop Lab -http://www.rlmueller.net
--- Hide quoted text -

- Show quoted text -

Hi Joe, thank you. I'm using an impersonation in the code and I've
checked that account used to make a request is correct.

Here's a piece of code (it's an asp.net app)

Response.Write(System.Security.Principal.WindowsIdentity.GetCurrent().Name);
....impersonation....
Response.Write(System.Security.Principal.WindowsIdentity.GetCurrent().Name);

DirectoryEntry entry = new DirectoryEntry("LDAP://.....");
DirectorySearcher searcher = new DirectorySearcher(entry);

searcher.Filter = "(objectCategory=computer)";
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("operatingsystem");

SearchResult r = searcher.FindOne();

string name = (string)r.Properties["name"][0].ToString();
string os = (string)r.Properties["operatingsystem"][0].ToString();

And here how it is working:

on XP:

box\ASPNET
....impersonation....
DOMAIN\account
....doing the request....
name = "comp999"
os = "Windows 2000 Professional"

On Win2003:

NT AUTHORITY\NETWORK SERVICE
....impersonation....
DOMAIN\account
....doing the request....
name = "comp999"
os = System.Byte[]

To get a string on the server I do some little hack as

foreach (byte t in byteArray)
{
char c = Convert.ToChar(t);
os += c.ToString();
}

then on the server I can read that

os = "Windows 2000 Professional"

It's not a big deal, I can live with it, but it's still interesting
why it work differently

BTW, DC is Win2000

Richard, thank you too, and special thanks for your website, I'm using
it quite often :)

-- Alexey [MVP ASP.NET]
 
J

Joe Kaplan

How is the imperstonation done? Are you just using <identity
impersonate="true"/> in web.config or are you programmatically impersonating
with a p/invoke call to LogonUser?

If you are doing the former and are using IWA auth in IIS, then the problem
is likely that of delegation and can be fixed by configuring Kerberos
delegation or deciding not to impersonate in the first place and using a
domain account for the process account instead.

If you are doing the latter, then it should work fine as it is.

Regarding your hack, it is better to use
System.Text.Encoding.UTF8.GetString(), passing in your byte array, to
convert the byte array to a string. That way, if the string happens to
contain any UTF8 data, it will get converted properly. It is also a little
less code. :)

Note that if impersonation is off, the Network Service account under 2003
SHOULD have rights to query the domain, as it uses the AD machine account
when accessing the network and that is a valid domain account. You may need
to supply a domain hint in your binding string to get the DC location to
work though. The path might look like LDAP://domain.com/DC=domain,DC=com
instead of LDAP://DC=domain,DC=com.

One other thing worth knowing is that if they ever upgrade the domain to
2003, it is likely that the code will break unless you fix the security
issue. Win2K AD allows anonymous searches by default, but 2K3 does not, so
if you accidentally bind as the anonymous user, you'll get an Operations
Error when you call FindAll.

I hope that helps.

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
--

Hi Joe, thank you. I'm using an impersonation in the code and I've
checked that account used to make a request is correct.

Here's a piece of code (it's an asp.net app)

Response.Write(System.Security.Principal.WindowsIdentity.GetCurrent().Name);
.....impersonation....
Response.Write(System.Security.Principal.WindowsIdentity.GetCurrent().Name);

DirectoryEntry entry = new DirectoryEntry("LDAP://.....");
DirectorySearcher searcher = new DirectorySearcher(entry);

searcher.Filter = "(objectCategory=computer)";
searcher.PropertiesToLoad.Add("name");
searcher.PropertiesToLoad.Add("operatingsystem");

SearchResult r = searcher.FindOne();

string name = (string)r.Properties["name"][0].ToString();
string os = (string)r.Properties["operatingsystem"][0].ToString();

And here how it is working:

on XP:

box\ASPNET
.....impersonation....
DOMAIN\account
.....doing the request....
name = "comp999"
os = "Windows 2000 Professional"

On Win2003:

NT AUTHORITY\NETWORK SERVICE
.....impersonation....
DOMAIN\account
.....doing the request....
name = "comp999"
os = System.Byte[]

To get a string on the server I do some little hack as

foreach (byte t in byteArray)
{
char c = Convert.ToChar(t);
os += c.ToString();
}

then on the server I can read that

os = "Windows 2000 Professional"

It's not a big deal, I can live with it, but it's still interesting
why it work differently

BTW, DC is Win2000

Richard, thank you too, and special thanks for your website, I'm using
it quite often :)

-- Alexey [MVP ASP.NET]
 
G

Guest

How is the imperstonation done?  Are you just using <identity
impersonate="true"/> in web.config or are you programmatically impersonating
with a p/invoke call to LogonUser?

If you are doing the former and are using IWA auth in IIS, then the problem
is likely that of delegation and can be fixed by configuring Kerberos
delegation or deciding not to impersonate in the first place and using a
domain account for the process account instead.

If you are doing the latter, then it should work fine as it is.

Regarding your hack, it is better to use
System.Text.Encoding.UTF8.GetString(), passing in your byte array, to
convert the byte array to a string.  That way, if the string happens to
contain any UTF8 data, it will get converted properly.  It is also a little
less code.  :)

Note that if impersonation is off, the Network Service account under 2003
SHOULD have rights to query the domain, as it uses the AD machine account
when accessing the network and that is a valid domain account.  You may need
to supply a domain hint in your binding string to get the DC location to
work though.  The path might look like LDAP://domain.com/DC=domain,DC=com
instead of LDAP://DC=domain,DC=com.

One other thing worth knowing is that if they ever upgrade the domain to
2003, it is likely that the code will break unless you fix the security
issue.  Win2K AD allows anonymous searches by default, but 2K3 does not,so
if you accidentally bind as the anonymous user, you'll get an Operations
Error when you call FindAll.

I hope that helps.

Joe, you know what? That helped me get the code working on Win2003.
You were absolutely right that the Network Service should work without
any impersonation. And it's amazing, but it solved the problem. I was
using WindowsImpersonationContext to programmatically impersonate a
current intranet user and for some reasons it returned so strange
result. Now, I get rid of the impersonation code (actually there is
just one line) and strings return as they should. The only thing is
that now my application could be installed on Win2003 (or later) but I
think I could live with it.

Thank you.
 
J

Joe Kaplan

Programmatic impersonation is something you should be able to make work as
well. If you like, you can show whatever code you were using with
WindowsImpersonationContext and we could discuss that. Most of that depends
on how the token for the user was generated. If you got that from IIS via
IWA auth, you still have a potential doublehop issue and would need
delegation regardless of whether the impersonation is done via web.config or
programmatically.

Either way, I'm glad you got it working. :)

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, you know what? That helped me get the code working on Win2003.
You were absolutely right that the Network Service should work without
any impersonation. And it's amazing, but it solved the problem. I was
using WindowsImpersonationContext to programmatically impersonate a
current intranet user and for some reasons it returned so strange
result. Now, I get rid of the impersonation code (actually there is
just one line) and strings return as they should. The only thing is
that now my application could be installed on Win2003 (or later) but I
think I could live with it.

Thank you.
 
G

Guest

Programmatic impersonation is something you should be able to make work as
well.  If you like, you can show whatever code you were using with
WindowsImpersonationContext and we could discuss that.  Most of that depends
on how the token for the user was generated.  If you got that from IIS via
IWA auth, you still have a potential doublehop issue and would need
delegation regardless of whether the impersonation is done via web.config or
programmatically.

The code is pretty simple as the following

System.Security.Principal.WindowsImpersonationContext
impersonationContext =

((System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();

in the web.config file I have

<authentication mode="Windows" />
<identity imperonate="false" />

As I already said it returns

on XP:

box\ASPNET
....impersonation....
DOMAIN\account

on Win2003:

NT AUTHORITY\NETWORK SERVICE
....impersonation....
DOMAIN\account

So, it works and I'm able to get the result of a query against AD on
WinXP (without impersonation of domain user account, local box\ASPNET
has no rights to do it) and even on Win2003 it works well, because
"name", "distinguishedName" and other attributes return values...
 
R

Richard Mueller [MVP]

Programmatic impersonation is something you should be able to make work as
well. If you like, you can show whatever code you were using with
WindowsImpersonationContext and we could discuss that. Most of that
depends
on how the token for the user was generated. If you got that from IIS via
IWA auth, you still have a potential doublehop issue and would need
delegation regardless of whether the impersonation is done via web.config
or
programmatically.

The code is pretty simple as the following

System.Security.Principal.WindowsImpersonationContext
impersonationContext =

((System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();

in the web.config file I have

<authentication mode="Windows" />
<identity imperonate="false" />

As I already said it returns

on XP:

box\ASPNET
.....impersonation....
DOMAIN\account

on Win2003:

NT AUTHORITY\NETWORK SERVICE
.....impersonation....
DOMAIN\account

So, it works and I'm able to get the result of a query against AD on
WinXP (without impersonation of domain user account, local box\ASPNET
has no rights to do it) and even on Win2003 it works well, because
"name", "distinguishedName" and other attributes return values...
---------
I'm not sure of the technical details, but "name" and "distinguishedName"
are different from attributes like operatingSystem, cn, sn, givenName, etc.
These attributes are not really saved in AD, but instead are "calculated" on
request from other attributes (similar to tokenGroups or canonicalName,
except those attributes are constructed/operational). It appears in your
case that you have permission to run a property method, like "Name", and
permission to read attribute values, but not permission to query the schema
partition. Can you verify that you have the same problem with attributes
like cn, sn, sAMAcountName, and givenName?
 
J

Joe Kaplan

This sounds like a standard double hop issue then if you use IWA auth in IIS
on 2003 server and access the server remotely. This CAN be made to work
just fine, but it requires you to implement Kerberos delegation in order to
do it. If you do some searches for double hop or Kerberos delegation,
you'll find lots of information about this. Start a new thread if you want
to talk about delegation though. :)

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
--
Programmatic impersonation is something you should be able to make work as
well. If you like, you can show whatever code you were using with
WindowsImpersonationContext and we could discuss that. Most of that
depends
on how the token for the user was generated. If you got that from IIS via
IWA auth, you still have a potential doublehop issue and would need
delegation regardless of whether the impersonation is done via web.config
or
programmatically.

The code is pretty simple as the following

System.Security.Principal.WindowsImpersonationContext
impersonationContext =

((System.Security.Principal.WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();

in the web.config file I have

<authentication mode="Windows" />
<identity imperonate="false" />

As I already said it returns

on XP:

box\ASPNET
.....impersonation....
DOMAIN\account

on Win2003:

NT AUTHORITY\NETWORK SERVICE
.....impersonation....
DOMAIN\account

So, it works and I'm able to get the result of a query against AD on
WinXP (without impersonation of domain user account, local box\ASPNET
has no rights to do it) and even on Win2003 it works well, because
"name", "distinguishedName" and other attributes return values...
 
G

Guest

Can you verify that you have the same problem with attributes
like cn, sn, sAMAcountName, and givenName?

hm, after a while it started to work as expected. I have tried to
check if I get properly cn, sn, sAMAcountName, and givenName and found
that it started to work for operatingsystem and
operatingsystemservicepack propeties too. Code, authentication,
everything are the same...
 
J

Joe Kaplan

One of the other "interesting" behaviors of the schema caching mechanism is
that ADSI attempts to cache the schema on the local file system to improve
performance. Basically, after it has downloaded the schema from the
directory the first time and processed it, it tries to dump that data to a
file and then add some data to the registry recording the directory, last
modified date and file name of the schema file. The idea is that the next
time it opens up a session, it will first check the last modified date on
the abstract schema object and if the locally cached version is up to date,
it will use that instead.

The problem with this behavior is that ADSI attempts to write all this data
to a directory in the Windows directory (schCache) and writes the registry
data to HKLM. Because you need admin rights to write to either of these
locations, the caching behavior usually doesn't work in ASP.NET web apps
where the executing security context is usually not Admin. So, these writes
silently fail. However, is someone executes and ADSI script on the machine
logged in as admin, then the write will succeed. So, there can be quite a
bit of mystery surrounding this.

I believe server 2008 now will try to write this data to the user profile
area of the registry and file system so they will succeed. However, they
may not then be readable by other users. I can't remember if SP2 of 2K3
server also added this.

Anyway, the schema caching stuff can be mysterious and frustrating at times.
However, if the security context executing the ADSI/S.DS code has
permissions to read the schema, it will always be able to download the
abstract schema from the directory and place it in memory.

Also, if you don't want to deal with the schema caching stuff, you can
always switch to programming in System.DirectoryServices.Protocols. It does
not look at the abstract schema and makes no attempt to map directory data
types to COM data types like ADSI does. The downside is that all data from
the directory comes back as strings (or byte arrays, take your pick) and you
end up having to write your own code to convert them to numbers and date
times if you want to treat them as strongly typed.

--
Joe Kaplan-MS MVP Directory Services Programming
Co-author of "The .NET Developer's Guide to Directory Services Programming"
http://www.directoryprogramming.net
--
Can you verify that you have the same problem with attributes
like cn, sn, sAMAcountName, and givenName?

hm, after a while it started to work as expected. I have tried to
check if I get properly cn, sn, sAMAcountName, and givenName and found
that it started to work for operatingsystem and
operatingsystemservicepack propeties too. Code, authentication,
everything are the same...
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,143
Latest member
DewittMill
Top