How to efficiently collect all (string) values for all attributes of an LDAP search in a `char*[]` using the OpenLDAP 2.6 C client library?

28 views Asked by At

I am implementing a specific LDAP search in C using the OpenLDAP 2.6 C client library. I know that the search only returns a specific set of attributes (namely mail, mailAlias, and mailAccount), because I only query for those attributes. Each of the three attributes might be multi-valued (i.e. appear several times in the result) and encodes an RFC822 mail address (i.e. [email protected]) as an IA5 string.

All I need is the union set of all values of all attributes as a char*[] which then will be further processed by the C program. I do not care which value belongs to which attribute. This means if the result of the search is something like

dn: uid=johndoe,ou=users,dc=my-domain,dc=tld
mail: [email protected]
mail: [email protected]
mailAccount: [email protected]
mailAlias: [email protected]

then I eventually want to have char* result[] = { "[email protected]", "[email protected]", "[email protected]", "[email protected]" }. The order and duplicates are not important. The C program will deal with that downstream. Note, if the search returns more than one entry, the result shall still be the union set of all values of all attributes of all entries.

Currently, my code looks essentially like this

LDAPMessage* ldap_result_msg = NULL;
int result_code = ldap_search_ext_s( ldap_handle, ..., &ldap_result_msg );
// ... error handling left out ...
struct string_array_t* result = create_string_array(); // a dynamic array very much like std::vector<std::string> in C++
for(
    LDAPMessage* ldap_entry_msg = ldap_first_entry( ldap_handle, ldap_result_msg );
    ldap_entry_msg != NULL;
    ldap_entry_msg = ldap_next_entry( ldap_handle, ldap_entry_msg )
) {
    BerElement* berptr;
    for(
        char* ldap_attr = ldap_first_attribute( ldap_handle, ldap_entry_msg, &berptr );
        ldap_attr != NULL;
        ldap_attr = ldap_next_attribute( ldap_handle, ldap_entry_msg, berptr )
    ) {
        struct berval** values = ldap_get_values_len( ldap_handle, ldap_entry_msg, ldap_attr );
        char* value = NULL;
        for( int i = 0; values[i] != NULL; ++i ) {
            value = malloc( values[i]->bv_len + 1 );
            strncpy( value, values[i]->bv_val, values[i]->bv_len );
            value[ values[i]->bv_len ] = '\0';
            push_onto_string_array( result, value );
            free( value );
        }
        ldap_value_free_len( values );
        ldap_memfree( ldap_attr );
    }
    ber_free( berptr, 0 );
}
ldap_msgfree( ldap_result_msg );

I have two concerns, I see two issues which might be improved:

1. Unnecessary inefficient walk over the LDAP result message

Currently, I iterate over each entry via ldap_next_entry, for each entry over each attribute via ldap_next_attribute and then retrieve the values for each specific attribute via ldap_get_values_len. If one looks at how ldap_get_values_len is implemented at OpenLDAP.org, LibLDAP, getvalues.c:59, then one sees that getvalues searches through the result from the beginning to find the provided attribute. Given the fact that I am only interested in the union of all string values, this appears very inefficient. Is there any way to traverse the result message in a single pass?

2. Unnecessary allocation/freeing of dynamic memory

The method push_onto_string_array( result, value ) makes a deep copy of the provided value anyway, i.e. the 2nd argument is a char * const. Nonetheless, the loop allocates and frees berval** values for each entry and attribute as well as makes another deep copy for each attribute value, adds a NUL at the end of the string and then passes this to push_onto_string_array( result, value ) which makes another copy of its own.

0

There are 0 answers