Troubleshooting ADSI Schema Cache
Normally, the ADSI schema cache just works behind the scenes and does what it is supposed to. However, there are several cases where it does not, and suddenly we lose the ability to read most attributes without weird exceptions. Some of the most frustrating problems we've seen developers suffer through are caused by schema caching. This is a topic that developers should understand well, since it tends to crop up with some regularity.
What Can Go Wrong?
Schema caching in ADSI imposes a number of preconditions that must be perfectly aligned for it to work correctly. The most obvious aspect is that we typically need an administrative-level account to write to HKLM in the registry or the Windows directory, yet this is something we obviously cannot be guaranteed of when our application runs. In fact, we can argue that in nearly all instances, we would not want this to be the case, especially in the context of an application servicing web browsers. In ASP.NET, the default settings for both IIS 5 and IIS 6 run the worker process as a low-privileged account, not an administrator.
The net result of reduced permissions is that the schema will simply not be cached and will be read directly from the directory for each process that loads ADSI against that LDAP directory. Luckily, this just makes things slower. It does not actually break anything.
However, what happens if we cannot read or interpret the schema at all? In this case, bad things are probably about to happen. If ADSI fails to get the schema for the current directory, it falls back to a built-in LDAP version 2 schema. If an attribute we happen to read or write is in this schema, then ADSI can map the LDAP data type to an ADSI and COM (and eventually .NET) data type and everything will work as we expect. However, if the attribute is not in the built-in schema, an exception will be thrown when trying to read or write it with the DirectoryEntry.
In .NET 1.x, the DirectorySearcher has the same problem, although the exception is different.
DirectorySearcher exception:
System.NotImplementedException "Handling of this
ADSVALUE type is not yet implemented (type = 0xb)"
DirectoryEntry exception:
System.Runtime.InteropServices.COMException (error
code 8000500c) "The Active Directory datatype cannot
be converted to/from a native DS Datatype"
Things improve slightly in version 2.0 with DirectorySearcher, as any unknown data types will automatically be marshaled as a byte array (byte[]), so we should not see System.NotImplementedException anymore. We still have to contend with converting all the returned data from byte arrays back into strings and such, but at least we can read the data.
Addressing Schema Mapping Problems
ADSI can have schema-mapping problems for three primary reasons.
- The subSchema object cannot be read due to a security problem.
- The target directory is not LDAP version 3 compliant and does not support a subSchema object via the subschemaSubentry attribute on RootDSE.
- ADSI has an internal error parsing the schema.
Security Related Schema Problems
Of the three errors, the first one is the only one that occurs with Active Directory and ADAM, so we will focus on that. Essentially, this problem tends to occur when the security context used to bind to the directory is not accepted by the server and the remote user ends up being authenticated as an anonymous user instead. Anonymous users do not have permission to read the subSchema object in either Active Directory or ADAM by default, so the LDAP version 2 schema is used.
This problem is especially significant in ASP.NET applications, as we should remember that the schema is cached only once per process.[a] In ASP.NET applications, multiple AppDomains are often loaded in the same process. When we find a situation where ASP.NET applications seem to have problems that are inconsistent, we need to make sure to check all of the applications in that process for any SDS or ADSI usage to make sure that none of them is binding anonymously. Otherwise, the first application to start up in that worker process will define the schema mapping for that particular directory for the life of the process. If that application happens to authenticate as an anonymous user, the schema will revert to LDAP version 2 and will cause problems for all applications running in the process.
The good news is that we can generally always find a way to correct this problem by fixing the security context. We discuss binding and security contexts in much greater detail in Chapters 3 and 8.
[a] In more-recent versions of ADSI, if a connection using different credentials accesses a directory and sees that the schema cache for that directory is in the default mode, it will attempt to download the schema again using the new credentials.
LDAP Compliance Problems
We probably can't do much to address LDAP compliance problems. For example, the directory may be an LDAP version 2 directory that does not support a RootDSE mechanism. It may also have a customized schema. In this case, no mechanism is available for ADSI to learn the schema, so it cannot map any data types for the custom attributes. The other version of this problem is that the directory has an incomplete LDAP version 3 implementation where there is no subschemaSubentry attribute on RootDSE.
In this case, our best bet is to use System.DirectoryServices.Protocols (SDS.P) in .NET 2.0. As we discussed in Chapter 2, this API is lower level and does not attempt to do any data-type conversion. As long as the directory is otherwise LDAP compliant, we should be able to get on with our work (at the expense of having to write more code to do the same things). If we do not have the option of going with .NET 2.0, then things get a little ugly.
Using the DirectoryEntry object, we can cast the NativeObject property to an IADsPropertyList object and use its GetPropertyItem method to get an IADsPropertyValue object. From there, we can attempt to convert the attribute value to whatever data type we choose. It is unpleasant and potentially slow, but it should work. In this situation, if we avoid using DirectorySearcher except to access very basic attributes like distinguishedName, then we might be able to cobble together a solution.
Schema Parsing Problems
These are rare, but they are also the most insidious to diagnose. Although we do not have any specific examples, it has been known to happen with some non-Microsoft directories that the abstract schema presented cannot be parsed by ADSI successfully, due to a bug in the ADSI implementation or in the format of the directory's subSchema data. In this case, ADSI will silently fail, leaving us with the default backup LDAP version 2 schema.
So, how can we tell if this is the problem? If the subSchema appears to be where it should and we can rule out security context issues blocking us from reading it, then this is a possibility.
The workarounds are similar to those suggested earlier. However, if we are interested in getting this fixed, this is the point where we should consider contacting Microsoft support. The Directory Services team is genuinely interested in correcting any flaws in ADSI and will want to hear about it. These bugs exist mainly because they are rarely communicated back to the product group. Don't be shy; they might even build you a hotfix.
|