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.