An Active Directory consists of a forest which contains domain trees, and domains.
The forest name is the name of the first domain that was created (Eg. antipodes.com),a domain tree is a contiguous name space in the forest (Eg. antipodes.com, australis.antipodes.com, van diemens.australis.antipodes.com) and you can also have discontiguous names for domain trees (Eg. antipodes.com, foobar.com, acme.com can all exist in the same forest)
So how do you perform a search across the entire forest, without having to visit every domain ?
The Global Catalog
The global catalog is a read-only naming context, that contains a partial set of the attributes for all objects in the forest. The GC is maintained as a separate naming context on domain controllers, and GC's replicate changes from other domain controllers hosting the global catalog in other domains.
From a client perspective, the GC simply responds to LDAP requests on port 3268 (or port 3269 if using SSL/TLS)
In practice, a client application would perform a forest wide search against the GC (using port 3268) and having found the object, then retrieves additional attributes or modifies attributes on a DC (using port 389) in the domain in which the object is located.
For example, imagine a forest, antipodes.com containing two domain trees, one being antipodes.com (also containing a domain australis.antipodes.com) and another at zealand.com.
A user in australis.antipodes.com wishes to perform a search for another user they know exists in the zealand domain. The user issues the search against a domain controller hosting the GC (for example server3.australis.antipodes.com) over port 3268. The user then retrieves the full distinguished name of the user in the zealand domain. The user then requests other attributes (not contained in the GC) by performing a search for the user against a domain controller. The user does not have to explicitly connect to a domain controller in the zealand domain, as they can use LDAP Referrals to guide them to a domain controller in the correct domain.
Some information on the GC can be found at:
http://support.microsoft.com/kb/257203
and
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ad/ad/binding_to_the_global_catalog.asp
Active Directory automatically generates LDAP referrals for all domains in the forest.maintains. These are generated from crossRef objects created in Partitions container in the configuration naming context. In fact you can allow DC's to generate referrals for other LDAP directories by creating your own cross reference objects. The attributes for a crossRef object are:
nCName - full distinguished name of LDAP naming context held on the other server (Eg. cn=otherdir,o=acme,c=US)
dnsRoor = dns name of the server hosting te other LDAP directory. (Eg. ldapsrv.acme.com)
objectClass=crossRef
The following sample code illustrates searching for an object in the global catalog, and then chasing a referral to a DC to retrieve other attributes that are not held by the GC.
/**
* searchgc.java
* 5 July 2001
* Sample JNDI application to perform a search against the Active Directory
* Global Catalog and follow referrals to DC's in different domains
*
*/
import java.util.Hashtable;
import javax.naming.ldap.*;
import javax.naming.directory.*;
import javax.naming.*;
publicclass searchgc {
publicstaticvoid main (String[] args) {
//set up one connection for the Global Catalog port; 3268
//and another for the normal LDAP port; 389
Hashtable envGC = new Hashtable();
Hashtable envDC = new Hashtable();
String adminName = "CN=Administrator,CN=Users,DC=ANTIPODES,DC=COM";
String adminPassword = "XXXXXXX";
//Note the GC port; 3268, and the normal LDAP port 389
//Just for the hell of it, lets use a different GC and DC
String urlGC = "ldap://mydc.antipodes.com:3268";
String urlDC = "ldap://mydc02.zealand.com:389";
envGC.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
envDC.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
//set security credentials, note using simple cleartext authentication
envGC.put(Context.SECURITY_AUTHENTICATION,"simple");
envGC.put(Context.SECURITY_PRINCIPAL,adminName);
envGC.put(Context.SECURITY_CREDENTIALS,adminPassword);
envDC.put(Context.SECURITY_AUTHENTICATION,"simple");
envDC.put(Context.SECURITY_PRINCIPAL,adminName);
envDC.put(Context.SECURITY_CREDENTIALS,adminPassword);
//connect to both a GC and DC
envGC.put(Context.PROVIDER_URL,urlGC);
envDC.put(Context.PROVIDER_URL,urlDC);
//We need to chase referrals when retrieving attributes from the DC
//as the object may be in a different domain
envDC.put(Context.REFERRAL,"follow");
try {
//Create the initial directory context for both DC and GC
LdapContext ctxGC = new InitialLdapContext(envGC,null);
LdapContext ctxDC = new InitialLdapContext(envDC,null);
//Now perform a search against the GC
//Create the search controls
SearchControls searchCtls = new SearchControls();
//Specify the attributes to return
String returnedAtts[]={"sn","givenName","mail","wwwHomePage"};
searchCtls.setReturningAttributes(returnedAtts);
//Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
//specify the LDAP search filter
String searchFilter = "(&(objectClass=user)(mail=*)(|(givenName=Albert)(givenName=Isaac)))";
//Specify the Base for the search
//an empty dn for all objects from all domains in the forest
String searchBase = "";
//initialize counter to total the results
int totalResults = 0;
//Search for objects in the GC using the filter
NamingEnumeration answer = ctxGC.search(searchBase, searchFilter, searchCtls);
//Loop through the search results
while (answer.hasMoreElements()) {
SearchResult sr = (SearchResult)answer.next();
totalResults++;
System.out.println(">>>" + sr.getName());
// Print out some of the attributes, catch the exception if the attributes have no values
Attributes attrs = sr.getAttributes();
if (attrs != null) {
try {
System.out.println(" name(GC): " + attrs.get("givenName").get() + " " + attrs.get("sn").get());
System.out.println(" mail(GC): " + attrs.get("mail").get());
}
catch (NullPointerException e) {
System.err.println("Problem listing attributes from Global Catalog: " + e);
}
}
//Now retrieve attributes from the DC
Attributes DCattrs = ctxDC.getAttributes(sr.getName());
try {
System.out.println(" Web(DC): " + DCattrs.get("wWWHomePage").get());
System.out.println(" Fax(DC): " + DCattrs.get("facsimileTelephoneNumber").get());
}
catch (NullPointerException e) {
System.err.println("Problem listing attributes from Domain Controller: " + e);
}
}
System.out.println("Total results: " + totalResults);
ctxGC.close();
ctxDC.close();
}
catch (NamingException e) {
System.err.println("Problem searching directory: " + e);
}
}
}
The code works great for searching and returning attributes from the Global Catalog, but I'm having some trouble with the "memberOf" attribute.
We have two subdomains at work: "BA" and "CORP". There are different groups in each subdomain.
Context.REFERRAL is set to "follow"
Here's my problem:
If I search the global catalog for a user in the "BA" domain, all groups that the user is a member of are returned. However, if the user is in the "CORP" domain, only top-level (global) groups are returned in the "memberOf" attribute. Maybe the query fails when it doesn't find the user in the "BA" domain?
I'm new to the whole LDAP querying, so I'm not quite sure how to approach this. Maybe I need to "throw" the Context.REFERRAL and catch any errors? I've tried this but don't really understand it...
Re: JNDI, Active Directory, Referrals and Global Catalog
Oct 5, 2005 12:48 AM
(reply 2
of 14) (In reply to
#1 )
For group memberships have a look at http://forum.java.sun.com/thread.jspa?threadID=580113&tstart=60 and http://forum.java.sun.com/thread.jspa?threadID=581444&tstart=150
Re: JNDI, Active Directory, Referrals and Global Catalog
Nov 9, 2005 5:06 PM
(reply 3
of 14) (In reply to
#2 )
This is great for searching, but how do I go about qurying what domain controllers are availiable on a given network if I dont know what the domain controllers are?
Re: JNDI, Active Directory, Referrals and Global Catalog
Nov 10, 2005 6:38 AM
(reply 4
of 14) (In reply to
#3 )
Ahh.. the good old chicken & egg question,
Fortunately there's a rooster....
If you refer to the posting at http://forum.java.sun.com/thread.jspa?threadID=674825&tstart=30 you can find a very simple sample of using JNDI to query DNS.
Active Directory domain controllers register srv resource records in dns.
Assuming you at least know your domain name, you can query a DNS server to find out which domain controller names have been registered.
Typcal records include:
Domain Names
_ldap._tcp.<DNSDomainName>
(Eg. _ldap._tcp.antipodes.com)
Global Catalogs
_ldap._tcp.gc._msdcs.<DNSDomainName>
(Eg. _ldap._tcp.gc._msdcs.antipodes.com)
All of this is documented at http://www.microsoft.com/technet/prodtechnol/windowsserver2003/library/TechRef/9d62e91d-75c3-4a77-ae93-a8804e9ff2a1.mspx
Having learnt how to retrieve the RR's, you could also get funky and duplicate the Windows locator functionality and evaluate the weight and priority records, or search for the "closest" DC, based on the client's IP subnet.
Once you have located a domain controller, you can now query the Active Directory for domain controllers by performing a LDAP search on the configuration name space for object of the ntDSDSA category.
A code snippet would look something like:
//Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
//specify the LDAP search filter
String searchFilter = "(objectCategory=nTDSDSA)";
//Specify the Base for the search
String searchBase = "CN=Configuration,DC=Antipodes,DC=Com";
There are several other ways of querying a domain name space for domain controllers, for example by searching for computer objects with the appropriate userAccountControl flags etc.
When the user attempts to access a protected URL, we
1. Run a JSP which requests the user name from Windows machine via NTLM (a 3-steps auto-iteraction with browser, exactly the code which you provided), and immediately redirects the user to a next Login JSP.
2. The Login JSP displays the user name and uses
<auth-method>FORM</auth-method>
to submit the "j_username" and "j_password" via "POST" to WebSphere for authenticatication.
Now is the problem: if we remove the NTLM part from the JSP (1) and simply redirect to the Login JSP, the WebSphere authenticates the user. If NTLM code is executed in first JSP, the WebSphere cannot authenticate the user and not returns any errors in the log.
Why FORM authentication in Websphere doesn't work if NTLM code is executed and and how to fix this?
Do I need administrative previlages to query a list of users in Active Directroy. When I tried to run the example with my user id, I ran into naming exception.
No, you do not require administrative credentials to query Active Directory, rather, you require valid credentials !
What was the exception ?
if it was LDAP error code 49, then it means that your credentials are invalid. Either an incorrect password or username.
For Active Directory, the username can be in either of three formats.
Distinguished Name:
cn = Albert Einstein,OU=Research,dc=Antipodes,dc=com
NT style Domain Name
ANTIPODES\alberte
User Principal Name
alberte@antipodes.com
Note that Windows Server 2003 does not permit anonymous bins to teh Active Directory and as per the LDAP standard, an anonymous logon may be defined as a credentials with a zero length password.
When one recieve a referral it typicall looks like CN=john,DC=ext,DC=bigcompany, DC=com . However if CN contatin a space like 'john doe' one recieve CN=john%20doe . This eventually cause invalid name exceptions in our application. Is this a unique 'feature' of AD or is the '%20' common in other LDAP servers?
Our application has an account in AD which search the directory, in some cases the GC, for the DN of a user. When it finds it it tries to bind with the DN and get some attributes. If the attributes is not present in the GC, (see http://support.microsoft.com/kb/248717/ on how to set this), one gets partial result exceptions. Now we have two possibilities to solve this; customer replicates the needed attributes to GC or we?re down to chasing referrals. If the customer is "tough" and one has to chase referrals there are as several possibilities. In this post you suggest that one configures the GC and all DC?s and search the attributes missing in the GC in the DC that has the attributes. In your code you have set the DC info "hard", guess you can read it from properties aswell. Another option is to DNS lookup and retrive DC information. However, my question:), does not the referral or DN contain all the necessary info for this? If you recieve the DN cn=john,dc=ext,dc=bigcompany,dc=com can?t you simply do a bind to ldap://ext.bigcompany.com to fetch the attributes for the user? Thus you will only need to know one GC in the forrest to have "access" to the info needed.
One last question and it may be inapropriate here but since you seem to be such a wiz kid on AD, if one replicates contact info attributes like mail-mobile-pager-iphone to the GC does that pose some performance issues for Active Directory? My customer made some remarks about that, most likely they are full of it like always but one never knows...:)
Thanks for great posts on JNDI/AD, although I?m not a big fan of AD one has to simple come to terms that they have one this war......
Your posts have been very helpful to me. But still one problem i couldn't solve. I am using Kerberos to authenticate to win active directory. I also used the code sample you provided her to search GC. When trying to get the attributes using distenguished name
GSSException: No valid credentials provided (Mechanism level: Attempt to obtain new INITIATE credentials failed! (null))
at sun.security.jgss.krb5.Krb5InitCredential.getTgtFromSubject(Unknown Source)
at sun.security.jgss.krb5.Krb5InitCredential.getInstance(Unknown Source)
at sun.security.jgss.krb5.Krb5MechFactory.getCredentialElement(Unknown Source)
at sun.security.jgss.GSSManagerImpl.getCredentialElement(Unknown Source)
at sun.security.jgss.GSSCredentialImpl.add(Unknown Source)
at sun.security.jgss.GSSCredentialImpl.<init>(Unknown Source)
at sun.security.jgss.GSSCredentialImpl.<init>(Unknown Source)
at sun.security.jgss.GSSManagerImpl.createCredential(Unknown Source)
at sun.security.jgss.GSSContextImpl.initSecContext(Unknown Source)
at sun.security.jgss.GSSContextImpl.initSecContext(Unknown Source)
at com.sun.security.sasl.gsskerb.GssKerberosV5.evaluateChallenge(GssKerberosV5.java:160)
at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:113)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.jndi.ldap.LdapClient.saslBind(Unknown Source)
at com.sun.jndi.ldap.LdapClient.authenticate(Unknown Source)
at com.sun.jndi.ldap.LdapCtx.connect(Unknown Source)
at com.sun.jndi.ldap.LdapCtx.<init>(Unknown Source)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(Unknown Source)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(Unknown Source)
at com.sun.jndi.url.ldap.ldapURLContextFactory.getObjectInstance(Unknown Source)
at javax.naming.spi.NamingManager.getURLObject(Unknown Source)
at javax.naming.spi.NamingManager.processURL(Unknown Source)
at javax.naming.spi.NamingManager.processURLAddrs(Unknown Source)
at javax.naming.spi.NamingManager.getObjectInstance(Unknown Source)
at com.sun.jndi.ldap.LdapReferralContext.<init>(Unknown Source)
at com.sun.jndi.ldap.LdapReferralException.getReferralContext(Unknown Source)
at com.sun.jndi.ldap.LdapCtx.c_getAttributes(Unknown Source)
at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_getAttributes(Unknown Source)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(Unknown Source)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(Unknown Source)
at javax.naming.directory.InitialDirContext.getAttributes(Unknown Source)
at javax.naming.directory.InitialDirContext.getAttributes(Unknown Source)
at wad.SearchWAD.doSearch(SearchWAD.java:66)
at SabicHelpdeskApp.main(SabicHelpdeskApp.java:18)
Caused by: javax.security.auth.login.LoginException: No LoginModules configured for com.sun.security.jgss.initiate
at javax.security.auth.login.LoginContext.init(Unknown Source)
at javax.security.auth.login.LoginContext.<init>(Unknown Source)
at sun.security.jgss.LoginUtility.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
... 37 more
javax.naming.AuthenticationException: GSSAPI [Root exception is com.sun.security.sasl.preview.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided]]
at com.sun.jndi.ldap.LdapReferralException.getReferralContext(Unknown Source)
at com.sun.jndi.ldap.LdapCtx.c_getAttributes(Unknown Source)
at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_getAttributes(Unknown Source)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(Unknown Source)
at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.getAttributes(Unknown Source)
at javax.naming.directory.InitialDirContext.getAttributes(Unknown Source)
at javax.naming.directory.InitialDirContext.getAttributes(Unknown Source)
at wad.SearchWAD.doSearch(SearchWAD.java:66)
at SabicHelpdeskApp.main(SabicHelpdeskApp.java:18)
Caused by: com.sun.security.sasl.preview.SaslException: GSS initiate failed [Caused by GSSException: No valid credentials provided]
at com.sun.security.sasl.gsskerb.GssKerberosV5.evaluateChallenge(GssKerberosV5.java:180)
at com.sun.jndi.ldap.sasl.LdapSasl.saslBind(LdapSasl.java:113)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at com.sun.jndi.ldap.LdapClient.saslBind(Unknown Source)
at com.sun.jndi.ldap.LdapClient.authenticate(Unknown Source)
at com.sun.jndi.ldap.LdapCtx.connect(Unknown Source)
at com.sun.jndi.ldap.LdapCtx.<init>(Unknown Source)
at com.sun.jndi.ldap.LdapCtxFactory.getUsingURL(Unknown Source)
at com.sun.jndi.ldap.LdapCtxFactory.getLdapCtxInstance(Unknown Source)
at com.sun.jndi.url.ldap.ldapURLContextFactory.getObjectInstance(Unknown Source)
at javax.naming.spi.NamingManager.getURLObject(Unknown Source)
at javax.naming.spi.NamingManager.processURL(Unknown Source)
at javax.naming.spi.NamingManager.processURLAddrs(Unknown Source)
at javax.naming.spi.NamingManager.getObjectInstance(Unknown Source)
at com.sun.jndi.ldap.LdapReferralContext.<init>(Unknown Source)
... 9 more
Caused by: GSSException: No valid credentials provided
at sun.security.jgss.GSSCredentialImpl.<init>(Unknown Source)
at sun.security.jgss.GSSCredentialImpl.<init>(Unknown Source)
at sun.security.jgss.GSSManagerImpl.createCredential(Unknown Source)
at sun.security.jgss.GSSContextImpl.initSecContext(Unknown Source)
at sun.security.jgss.GSSContextImpl.initSecContext(Unknown Source)
at com.sun.security.sasl.gsskerb.GssKerberosV5.evaluateChallenge(GssKerberosV5.java:160)
... 26 more
Re: JNDI, Active Directory, Referrals and Global Catalog
Apr 9, 2008 12:11 PM
(reply 13
of 14) (In reply to
#12 )
Apologies for bumping up an old thread, but Victor, if you're still around did you find a solution to your problem? I'm (and it seems Frank above, too) am experiencing almost identical issues. I've searched high and low (Google and SDN both fruitless) to absolutely no avail and I'm now officially going out of my mind.
Can anyone shed any light on this? Please? :)
Thanks...
Roman R.
Find UK Blinds and much more at UrbaneBlinds.co.uk.