Search code examples
javaactive-directoryldapunboundid-ldap-sdk

LDAP query doesn't return correct data from Active Directory


I'm working on a tool to get user details from the AD and import them into another system. We were planning on using the objectSid as the unique identifier but I've found that for some reason, the objectSid in the LDAP result does not match what's in Active Directory. Most of the bytes are the same but there are some there are different and sometimes LDAP results have fewer bytes than there are in the AD.

objectSid from user in AD:

decimal: [ 1,  5,  0,  0,  0,  0,  0,  5, 21,  0,  0,  0, 35, 106, 222, 96, 236, 251, 239, 68, 32, 255, 234, 203, 122,  4,  0,  0]
hex:     [01, 05, 00, 00, 00, 00, 00, 05, 15, 00, 00, 00, 23,  6A,  DE, 60,  EC,  FB,  EF, 44, 20,  FF,  EA,  CB,  7A, 04, 00, 00]

objectSid for same user via LDAP result:

decimal: [ 1,  5,  0,  0,  0,  0,  0,  5, 21,  0,  0,  0, 35, 106,  63, 96,  63,  63,  63, 68, 32,  63,  63,  63, 122,  4,  0,  0]
hex:     [01, 05, 00, 00, 00, 00, 00, 05, 15, 00, 00, 00, 23,  6A,  3F, 60,  3F,  3F,  3F, 44, 20,  3F,  3F,  3F,  7A, 04, 00, 00]

It almost seems as if any value over 128 comes back as 63/3F in the LDAP result. For another user, the LDAP result is missing 1 byte (the question marks):

hex from AD:   [01 05 00 00 00 00 00 05 15 00 00 00 23 6A DE 60 EC FB EF 44 20 FF EA CB 88 04 00 00]
hex from LDAP: [01 05 00 00 00 00 00 05 15 00 00 00 23 6A 3F 60 3F 3F 3F 44 20 3F 3F 3F ?? 04 00 00]

Here's the main portion of the code I'm using to do these tests.

final String ldapADServer = "ldap://" + cmdLine.getOptionValue("ldap");
final String bindDN = cmdLine.getOptionValue("u");
final String bindCredential = cmdLine.getOptionValue("p");
final String baseCtxDN = cmdLine.getOptionValue("d");

final Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, bindDN);
env.put(Context.SECURITY_CREDENTIALS, bindCredential);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapADServer);
env.put("com.sun.jndi.ldap.trace.ber", System.err);

final LdapContext ctx = new InitialLdapContext(env, null);

final String searchFilter = "(&(objectClass=user) (sAMAccountName=" + accountName + "))";

final SearchControls searchControls = new SearchControls();
searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);

final StringBuilder builder = new StringBuilder();
final NamingEnumeration<SearchResult> results = ctx.search(baseCtxDN, searchFilter, searchControls);
while (results != null && results.hasMoreElements()) {
    final SearchResult result = results.nextElement();
    builder.append(LdapHelper.getSearchResultDetails(result, ""));
}

logger.info("Search results: {}{}", StringUtils.NEW_LINE, builder.toString());

The LdapHelper simply loops through all attributes and returns them in a nicely formatted string. The objectGUID and objectSid are printed in hex format.

I was running the test using JRE 6 as well as JRE 7 with the same result. Our AD server is Window Server 2008 RC2 and I've tried to use both AD ports, 389 and 3268.

I'm going to look into other Java LDAP libraries now but I wanted to see if anyone else had run into these issues or does anyone know why this is and how to get around it? I.e. is there a way to get the proper values from AD?


Solution

  • I've now done the same using the UnboundID LDAP SDK and this works properly and returns the full and correct objectSid as well as objectGUID. So this seems to be a bug in the standard J2SE library?

    Code to do that in case anyone is interested:

    private static void unboundIdLdapSearch(final String ldapADServer, final String bindDN, final String bindCredential, final String baseCtxDN, final String userName) throws LDAPException, Exception {
        final LDAPConnection connection = new LDAPConnection(ldapADServer.substring(0, ldapADServer.indexOf(':')),
            Integer.parseInt(ldapADServer.substring(ldapADServer.indexOf(':') + 1)), bindDN, bindCredential);
        findAccountByAccountName(connection, baseCtxDN, userName);
        connection.close();
    }
    
    private static void findAccountByAccountName(final LDAPConnection connection, final String baseCtxDN, final String accountName) throws Exception {
    
        final String searchFilter = "(&(objectClass=user)(sAMAccountName=" + accountName + "))";
    
        logger.info("LDAP search filter: {}", searchFilter);
    
        final SearchRequest request = new SearchRequest(baseCtxDN, SearchScope.SUB, searchFilter);
        final com.unboundid.ldap.sdk.SearchResult result = connection.search(request);
        final int numOfResults = result.getEntryCount();
        final StringBuilder builder = new StringBuilder();
        builder.append("Search returned with ").append(numOfResults).append(" results: ").append(StringUtils.NEW_LINE);
        for (final SearchResultEntry entry : result.getSearchEntries()) {
            builder.append(LdapHelper.getSearchResultDetails(entry, ""));
        }
    
        logger.info("Search results: {}{}", StringUtils.NEW_LINE, builder.toString());
    }
    

    In addition, I happened to stumble across why the JNDI LDAP method didn't work properly for objectSid and objectGUID and got it working in addition to my UnboundID solution.

    First of all, I realized that when I used the UnboundID method of 'getValue' which returns a string, it also returned the same values the J2SE JNDI version did which is when I figured out that this does a String conversion to UTF-8 of the imported value.

    I then happened to come across another blog post (http://www.jroller.com/eyallupu/entry/java_jndi_how_to_convert) as well as this page: http://docs.oracle.com/javase/jndi/tutorial/ldap/misc/attrs.html . So all that's needed in order to get the objectSid and objectGUID properly is to add them to the list of binary attributes by adding a space separated list of attribute names to the map for the LDAP context:

    env.put("java.naming.ldap.attributes.binary", "objectSid objectGUID");