Querying Active Directory with Unix LDAP tools.

I want to run ldap queries against an AD, after all, AD is just LDAP, right?

Finding an AD server which to query can be done a number of ways. If you are on windows, an easy approach may be to use WMI or ADSI to ask AD where a domain controller is, but lets say you aren’t on windows. AD requires this information in DNS. One easy lucky way is server naming. You know that servers named Domain Controller or “DC” are probably a domain controller. I use the host command which comes with ISC BIND.


$ host -av myaddomain.myinetdomain.net

will give you a list (can be very long depending on your organization) of servers.

If that doesn’t show me what I what, I use this information How Domain Controllers are Located in Windows XP


$ host -av _ldap._tcp.myaddomain.myinetdomain.net

gives me a list of domain controllers which are listening for LDAP requests.

Now I can use ldapsearch to get the RootDSE. This doesn’t even require binding to the LDAP Directory


$ ldapsearch -x -h 192.168.199.10 -b '' -s base '(objectclass=*)'

Now the namingContexts attributes give me points off of which to start my searches. If you are familiar with AD then you should be familiar with these naming contexts. Of course one reflects my ad. e.g. dc=myaddomain,dc=myinetdomain,dc=net

If you happen to be in a large organization, take notice about some hints an helpers here. The serverName attribute often reflects the organizational structure of the AD. Is the AD organized by geography, business unit, or something else? The organizational unit in which this server is placed can give you hints.

From this point on you need to bind to the active directory. Now your account could be in any OU, so how do you know if you are cn=jsmith,cn=salespeople,cn=blah….. or if you are cn=jsmith,cn=Users,cn=blah… Well, quite cleverly, Microsoft extended the LDAP spec a bit and allows for binding with what they call a “User Principal Name”. You can login much like you would to a Windows computer.


$ ldapsearch -x -h 192.168.199.10 -b 'dc=myaddomain,dc=myinetdomain,dc=net' -s base -D 'jsmith@myaddomain.myinetdomain.net' -W

We know the domain portion of the User Principal Name from our naming contexts search before.

Now we can build all sorts of nice LDAP search expressions. Want to see all the computers in the domain?


$ ldapsearch -x -h 192.168.199.10 -b 'dc=myaddomain,dc=myinetdomain,dc=net' -s base -D 'jsmith@myaddomain.myinetdomain.net' -W '(objectclass=Computer)'

You can use AD’s Abiguous Name Resolution:


$ ldapsearch -x -h 192.168.199.10 -b 'dc=myaddomain,dc=myinetdomain,dc=net' -s base -D 'jsmith@myaddomain.myinetdomain.net' -W '(anr=smith*)'

Ultimately I want to search for computers in the Active Directory which have not been used in a while. Forgetting for a moment that lastLogon is not replicated in AD, how can we do this? or how can we even tell what that lastLogon value means. When I look at my ldap entry I get this for the lastLogon attribute:


$ ldapsearch -x -h 192.168.199.10 -b 'dc=myaddomain,dc=myinetdomain,dc=net' -s base -D 'jsmith@myaddomain.myinetdomain.net' -W '(anr=smith*)' lastLogon
...
accountExpires: 9223372036854775807
lastLogon: 128082628734460625
pwdLastSet: 128074920542439359

It turns out (and this is no joke) that this number is the time in some ANSI standard that is the number of 100 nanosecond intervals since January 1st, 1601. Ugh… There is much good information about this in the Dandelions technet article. I solved the problem of reading this using pythons AWESOME datetime module.


#!env python  from sys import argv
from datetime import datetime,timedelta
ansiTimeStart = datetime(1601,1,1)
lastLogon = timedelta(seconds= long(argv[1]) / 10000000)
#rfc822- with borked timezone 
if len(argv) and argv[2]=='-r':
print (ansiTimeStart+lastLogon).strftime("%a, %d %b %Y %H:%M:%S +0000" )
else:
#or ISO 8601
print (ansiTimeStart+lastLogon).isoformat()

So now I can send one of those crazy ansi dates and get a real date in either rfc822 format or iso8601 format.


$ ./ansidate 128082628734460625 -r
Fri, 17 Nov 2006 18:47:53 +0000
$ ./ansidate 128082628734460625
2006-11-17T18:47:53

Now what if I want to query for all computer records in a domain which have not had thier password set since August 1st, 2006. I have to find that crazy date. I just use python interactive for this.


$ python
...
>>>(datetime(2006,8,1)-datetime(1601,1,1)).days*3600*24*10000000
127988640000000000L

Now I can search.


$ ldapsearch -x -h 192.168.199.10 -b 'dc=myaddomain,dc=myinetdomain,dc=net' -s base -D 'jsmith@myaddomain.myinetdomain.net' -W '(&(lastLogon< =127988640000000000)(objectclass=computer)'

Note that ldap search filters don't have a less than operator, you must use less than or equal. a filter of '(lastLogin>1)' will fail with a Bad search filter error.

Using LDAP to query Active Directory is a natural fit, especially if you have LDAP experience in other applications. All of your LDAP knowledge should be applicable to Active Directory. I've found that much of the details on the AD schema attributes are not mentioned in most Microsoft documentation. It is best to go directly to the Windows SDK. Each attribute is well documented there.

3 thoughts on “Querying Active Directory with Unix LDAP tools.”

  1. Hey, I just found this article trying to get LDAP-based authentication going against AD. The trick where you specify the bind DN as an email address just saved my butt! I was about ready to throw in the towel with my client, who is fairly LDAP-ignorant (as much as I am AD-ignorant!), and I found this trick. It worked perfectly, and allowed me to keep moving.

    Thanks a ton!

  2. Thanks. this was great to know. I did not knew about this and finally ended up knowing that there are even man pages for ldapsearch.

    Thanks a lot.

Comments are closed.