I just happened to discover what I consider a bug in Windows Server 2003 SP1 Active Directory and ADAM SP1/R2.
I am sure what I am hitting was done on purpose but I don’t understand why and think it is a silly change and the fact that it breaks something in ADFIND just makes me cranky about it. I can’t find any public documentation about the change and the error message that it generates is at best wrong and completely misleading.
So I was playing around with some of the new options of adfind making sure I am cleaning up all of the little things that get dorked up when I work on a new version and I noticed that out of the blue, I am getting an operations errors
Error 0x1 (1) - Operations Error
when trying to pull the ROOTDSE without authentication. I tell adfind to dump extended error info and that gets me a little more info
Extended Error: 000004DC: LdapErr: DSID-0C09062B, comment: In order to perform this operation a successful bind must be completed on the connection., data 0, vece
running Err against 4DC tells me a little more
F:\Dev\CPP\AdFind>err 4dc
# for hex 0x4dc / decimal 1244 :
ERROR_NOT_AUTHENTICATED winerror.h
# The operation being requested was not performed because the
# user has not been authenticated.
# 1 matches found for "4dc"
Ok, this isn’t cool, accessing the rootdse, at least the basic stuff should all be able to be done anonymously.
So my first thought is what did I break? I jump over to my main development machine that has the latest compiled version of adfind and try it and it works, so I am like WTF! So I copy it to my main desktop machine and run it and it throws an error again… Then I realize that it is hitting two different DCs from the two machines, one is SP1 and one is RTM… Uh oh.
I kick adfind into debug mode and notice that the error is being thrown when adfind is trying to pull the rootdse for the second time. That sounds odd but let me explain, ADFIND works in three query phases
1. Phase 1, gather some basic data from the rootdse so it can process the switches supplied, this means retrieving the naming contexts and a couple of items to determine the AD version. Really simple stuff based on a bare bones ldap query.
2. Phase 2, Retrieve a portion of the schema depending on switches provided so it can decode items as needed
3. Phase 3, run a paged query with all of the necessary controls specified for all switches, etc. This is the primary stage of the entire program, 90% of the logic and 98% of the code is buried here.
When you try to pull the rootdse anonymously on Windows Server 2003 you need to chop out phase 2 because you can’t read the schema anonymously, this is easy to accomplish with adfind via the -dloid switch which stands for “Don’t Load OIDs”, i.e. don’t get the decode info. In this case it falls back to some hard coded values which is fine for a rootdse query. That means it only hits the Phase 1 query to get the basic info to handle switches and then hits the Phase 3 query to get the real information.
Since it fails on the second query it obviously isn’t an issue with authentication because if it truly were an authentication issue the problem would be at the first query. I created a little QND app that allows me to quickly change the type of query I am doing (basic versus paged) and sure enough, anytime that paged control is in the query, the query fails to allow you to retrieve info anonymously.
So for some reason, someone at MS decided that you shouldn’t be able to query the RootDSE with a paged query. While a paged query isn’t required for the RootDSE, it certainly makes it nicer and easier to have one main mechanism for handling the main query loop for Phase 3 above versus multiple mechanisms depending on whether I am retrieving the rootdse versus anything else.
In general I tell folks to use paged queries everywhere they can because there is less chance that they will get bit by it later in the event that all of a sudden they end up querying something unexpected and returning more than 1000 objects. You should be able to have one single query function to handle any query to AD, you just tell it what controls, etc that you need and then that function handles all paging and ranging and everything else under the covers for whatever is calling it.
If it weren’t that I built adfind from preexisting pieces I wouldn’t have been able to have easily chased this down because the Phase 1 query would have used the same processes as the Phase 3 and it simply would have failed at Phase 1. The error messages were worthless for this problem so it would have required much deeper digging if I could figure it out at all.
I could visualize other folks hitting this so hopefully documenting this with the DSID and error message above maybe folks will find it through google when they run into the problem.
So to start the path to resolution I pinged a friend at MS that I highly respect to get his feedback on what I am seeing as he tends to ask really good question in response to my questions and makes me think of things I might not have otherwise thought of. More than likely I will be “bugging” this in the bug feedback system available to me.
That is actually the second or third bug in one week. I am on a roll…
Incredible. Every time I read your blog, I am astounded at just how much I don’t know about AD.
Wow, easily replicated with LDP against a 2003 AD with SP1.
I don’t see why you would ever do a paged search against RootDSE, which is a base query and can only ever return 1 object. I mean, you don’t really *need* to do a paged search here, right? 🙂
At the same time, the behavior does seem to have changed for no particular reason. Maybe there is something about a paged query that does require a bind and we just don’t know it? Perhaps a subtle security flaw was found?
Eric? Dmitri? May we have an explanation please?
This is not a bug, this is intentional. We do not allow almost all LDAP controls to be used before you bind (the only control we do allow is for extended DNs). It has nothing to do with you querying RootDSE, only to do with the fact that you are doing it before you have authenticated. So if you auth, then you can do this.
We did this first in SP1.