You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



Mozilla LDAP C SDK Programmer's Guide

Chapter 5 - Using the LDAP API

This chapter covers some of the general LDAP API functions that are commonly used when writing LDAP clients. This chapter includes instructions on getting version information, freeing memory, checking for errors, and requesting synchronous and asynchronous functions.

Getting Information About the SDK

You can get version information about the particular version of the Mozilla LDAP C SDK that you are using (for example, the version of the SDK or the highest version of the LDAP protocol that it supports).

To get this version information, call the ldap_get_option() function using the following option:

ldap_get_option (..., LDAP_OPT_API_INFO, ...) ;

The following example illustrates retrieving the version information:

Code Example 5-1 - Retrieving LDAP version information

#include <stdio.h>
#include "ldap.h"

main()
{

LDAPAPIInfo         ldapi;
LDAPAPIFeatureInfo fi;
int                 i;
int                 rc;
LDAP                *ld;

memset( &ldapi, 0, sizeof(ldapi));
ldapi.ldapai_info_version = LDAP_API_INFO_VERSION;

if ((rc = ldap_get_option( ld, LDAP_OPT_API_INFO, &ldapi)) != 0) {
    printf("Error: ldap_get_option (rc: %d)\n", rc);
    exit(0);
}

printf("LDAP Library Information -\n"
    "  Highest supported protocol version: %d\n"
    "  LDAP API revision:         %d\n"
    "  API vendor name:          %s\n"
    "  Vendor-specific version:      %.2f\n",
    ldapi.ldapai_protocol_version, ldapi.ldapai_api_version,
    ldapi.ldapai_vendor_name,
    (float)ldapi.ldapai_vendor_version / 100.0 );

if ( ldapi.ldapai_extensions != NULL ) {
    printf("  LDAP API Extensions:\n");

    for ( i = 0; ldapi.ldapai_extensions[i] != NULL; i++ ) {
      printf("    %s", ldapi.ldapai_extensions[i] );
      fi.ldapaif_info_version = LDAP_FEATURE_INFO_VERSION;
      fi.ldapaif_name = ldapi.ldapai_extensions[i];
      fi.ldapaif_version = 0;

      if ( ldap_get_option( NULL, LDAP_OPT_API_FEATURE_INFO, &fi )
          != 0 ) {
        printf("Error: ldap_get_option( NULL,"
            " LDAP_OPT_API_FEATURE_INFO, ... ) for %s failed"
            " (Feature Info version: %d)\n",
            fi.ldapaif_name, fi.ldapaif_info_version );
      } else {
        printf(" (revision %d)\n", fi.ldapaif_version);
      }
    }
  }
  printf("\n");
}

Managing Memory

Several of the LDAP API functions allocate memory when called. When you have finished working with data allocated by these functions, you should free the memory.

Table 5-1 lists some of the API functions that allocate memory and the corresponding functions that you must call to free the memory when you are done.

Table 5-1 - API functions that allocate and free memory

Functions to free memory Type of memory freed
ldap_unbind() or ldap_unbind_s() Frees LDAP structures allocated by calling ldap_init().
ldap_msgfree() Frees LDAPMessage structures allocated by calling ldap_result() or ldap_search_ext_s().
ldap_ber_free() Frees BerElement structures allocated by calling ldap_first_attribute().
ldap_value_free() Frees char ** arrays structures allocated by calling ldap_get_values().
ldap_value_free_len() Frees Arrays of berval structures allocated by calling ldap_get_values_len().
ber_bvfree() Frees berval structures allocated by calling ldap_extended_operation_s(), ldap_parse_extended_result(), ldap_parse_sasl_bind_result(), and ldap_sasl_bind_s().
ldap_free_friendlymap() Frees FriendlyMap structures allocated by calling ldap_friendly_name().
ldap_free_urldesc() Frees LDAPURLDesc structures allocated by calling ldap_url_parse().
ldap_getfilter_free() Frees LDAPFiltDesc structures allocated by calling ldap_init_getfilter() or ldap_init_getfilter_buf().
ldap_mods_free() Frees LDAPMod ** arrays and structures allocated by functions that you call when you add or modify entries.
ldap_free_sort_keylist() Frees LDAPsortkey ** arrays structures allocated by calling ldap_create_sort_keylist().
ldap_control_free() Frees LDAPControl structures allocated by calling ldap_create_sort_control() or ldap_create_persistentsearch_control().
ldap_controls_free() Frees LDAPControl ** arrays structures allocated by calling ldap_get_entry_controls(), ldap_parse_result(), or ldap_parse_reference().
ldap_memfree() Any other types of memory that you allocate (this function is a general function for freeing memory).

See the descriptions of individual functions in Chapter 18 - Function Reference for more information about memory management.

Reporting Errors

In the LDAP protocol, the success or failure of an operation is specified by an LDAP result code sent back to the client. A result code of 0 normally indicates that the operation was successful whereas a non-zero result code usually indicates that an error occurred.

The following sections explain more about handling and reporting errors:

Getting Information About the Error

When an error occurs in an LDAP operation, the server sends the following information back to the client:

  • The LDAP result code for the error that occurred.
  • A message containing any additional information about the error from the server.

If the error occurred because an entry specified by a DN could not be found, the server may also return the portion of the DN that identifies an existing entry. (See "Receiving the Portion of the DN Matching an Entry" for an explanation.)

There are two ways you can get this information back from the server:

Receiving the Portion of the DN Matching an Entry

According to the LDAPv3 protocol, if a server returns an LDAP_NO_SUCH_OBJECT, LDAP_ALIAS_PROBLEM, LDAP_INVALID_DN_SYNTAX, or LDAP_ALIAS_DEREF_PROBLEM result code, the LDAP server should also send back the portion of DN that matches the entry that is closest to the requested entry.

For example, suppose the LDAP server processes a request to modify the entry with the DN "uid=bjensen,ou=Contractors,dc=example,dc=com" but that entry does not exist in the directory.

  • If the entry with the DN "ou=Contractors,dc=example,dc=com" does exist, the server sends this portion of the DN ("ou=Contractors,dc=example,dc=com") with the result code LDAP_NO_SUCH_OBJECT.
  • If the entry with the DN "ou=Contractors,dc=example,dc=com" does exist either, but the entry with the DN "dc=example,dc=com" does exist, the server sends "dc=example,dc=com" back to the client with the result code LDAP_NO_SUCH_OBJECT.

    Basically, the server moves back up the directory tree (one DN component at a time) until it can find a DN that identifies an existing entry.

Getting the Information from an LDAPMessage Structure

If you have requested the operation through an asynchronous function, not a synchronous function (see "Calling Synchronous and Asynchronous Functions" for the difference between these functions), you can get the result of the operation from the server by calling the ldap_result() function.

This function passes the result as an LDAPMessage structure. You can get information from this structure by calling the ldap_parse_result() function:

LDAP_API(int) LDAP_CALL ldap_parse_result( LDAP *ld,
  LDAPMessage *res, int *errcodep, char **matcheddnp,
  char **errmsgp, char ***referralsp,
  LDAPControl ***serverctrlsp, int freeit );

The different types of information are returned in the following parameters of this function:

  • The LDAP result code is the errcodep argument.
  • Additional information from the server is passed back as the errmsgp argument.
  • In cases when the server cannot find an entry from a DN, the portion of the DN that identifies an existing entry is passed back as the matcheddnp argument. (See "Receiving the Portion of the DN Matching an Entry" for details.)

Note that you can also get the error message describing the LDAP result code by using the ldap_err2string() function. (See the section "Getting the Error Message" for details.)

For a listing and descriptions of the different LDAP result codes, see Chapter 19 - Result Codes.

The following section of code gets and prints information about an error returned from the server.

Code Example 5-2 - Receiving and printing error codes from an LDAPMessage structure

#include <stdio.h>
#include "ldap.h"
...
LDAP              *ld;
LDAPMessage       *res;
int               msgid = 0, rc = 0, parse_rc = 0, finished = 0;
char              *matched_msg = NULL, *error_msg = NULL;
char              **referrals;
LDAPControl       **serverctrls;
struct timeval    zerotime;
...
while ( !finished ) {
  /* Check to see if the server returned a result. */
  rc = ldap_result( ld, msgid, 0, &zerotime, &res );
  switch ( rc ) {
  ...
  default:
    /* The client has received the result of the LDAP operation. */
    finished = 1;

    /* Parse this result to determine if the operation was successful. 
    parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg, 
      &error_msg, &referrals, &serverctrls, 1 );

    /* Verify that the result was parsed correctly. */
    if ( parse_rc != LDAP_SUCCESS ) {
      fprintf( stderr, "ldap_parse_result error: %s\n", 
        ldap_err2string( parse_rc ) );
      ldap_unbind( ld );
      return( 1 );
    }

    /* Check the results of the operation. */
    if ( rc != LDAP_SUCCESS ) {

      /* Print the error message corresponding to the result code. */
      fprintf( stderr, "Error: %s\n", 
        ldap_err2string( rc ) );

      /* If the server sent an additional message, print it out. */
      if ( error_msg != NULL && *error_msg != '\0' ) {
        fprintf( stderr, "%s\n", error_msg );
      }

      /* If the server cannot find an entry with the specified DN, 
        it may send back the portion of the DN that matches 
        an existing entry, For details, see 
        "Receiving the Portion of the DN Matching an Entry". */
      if ( matched_msg != NULL && *matched_msg != '\0' ) {
        fprintf( stderr, 
          "Part of the DN that matches an existing entry: %s\n", 
          matched_msg );
      }

      /* Disconnect and return. */
      ldap_unbind( ld );
      return( 1 );
    } 
...

Getting the Information from an LDAP Structure

In situations where you don't get an LDAPMessage structure (for example, if you are calling functions that do not interact with the server), you can get error information from the connection handle (the LDAP structure).

To get information about the last error that has occurred, call the ldap_get_lderrno() function:

LDAP_API(int) LDAP_CALL ldap_get_lderrno(LDAP *ld, char **m, char **s);

The different types of information are returned in the following ways:

  • The LDAP result code is returned by this function.
  • Additional information from the server is passed back as the s argument.
  • In cases when the server cannot find an entry from a DN, the portion of the DN that identifies an existing entry is passed back as the m argument. (See "Receiving the Portion of the DN Matching an Entry" for details.)

If you do not need to use the parameters returned by the ldap_get_lderrno() function, set the parameters to a NULL value. For example:

ldap_get_lderrno( ld, NULL, NULL );

The following section of code gets and prints information about an error.

Code Example 5-3 - Getting an error message from an LDAP structure

#include <stdio.h>
#include "ldap.h"
...
LDAP     *ld;
char*    *error_msg = NULL, *matched_msg = NULL;
int      rc;
...
rc = ldap_get_lderrno( ld, &matched_msg, &error_msg );
fprintf( stderr, "ldap_result error: %s\n", ldap_err2string( rc ) );
if ( error_msg != NULL && *error_msg != '\0' ) {
  fprintf( stderr, "%s\n", error_msg );
}

/* If the server cannot find an entry with the specified DN, 
  it may send back the portion of the DN that matches 
  an existing entry, For details, see "Receiving the Portion of the DN Matching an Entry". */
if ( matched_msg != NULL && *matched_msg != '\0' ) {
  fprintf( stderr, 
    "Part of the DN that matches an existing entry: %s\n", 
    matched_msg );
}
...

Getting the Error Message

If you have an error code and want to retrieve its corresponding error message, call the ldap_err2string() function. The function returns a pointer to the error message. For example:

Code Example 5-4 - Returning an error message from an error code

#include <stdio.h>
#include "ldap.h"
...
int      rc;
...
if ( rc != LDAP_SUCCESS ) {
  fprintf( stderr, "Error: %s\n", ldap_err2string( rc ) );
}
...

Note that the pointer returned by this function is a pointer to static data; do not free this string.

Setting Error Codes

When an LDAP operation is performed, the error information from the operation is specified in the LDAP structure. If you want to set error codes and error information in the LDAP structure, call the ldap_set_lderrno() function.

The following section of code sets the LDAP_PARAM_ERROR error code in the LDAP structure.

Code Example 5-5 - Setting an error code

#include "ldap.h"
...
LDAP *ld;
char *errmsg = "Invalid parameter";
...
if ( ldap_my_function() != LDAP_SUCCESS ) {
  ldap_set_lderrno( ld, LDAP_PARAM_ERROR, NULL, errmsg );
  return( 1 );
}
...

To print out the error message describing the last error that occurred, call the ldap_get_lderrno() function.

This example prints a message if a function fails to delete an entry in the server.

Code Example 5-6 - Printing error codes

#include "ldap.h"
...
int lderr;
char * errmsg;
LDAP *ld;
char *dn = "uid=bjensen, ou=People, dc=example,dc=com";
...
if ( ldap_delete_s( ld, dn ) != LDAP_SUCCESS ) {
  lderr = ldap_get_lderrno (ld, NULL, &errmsg); 
  if ( errmsg, != NULL ) {
    fprintf(stderr, "ldap_delete_s: %s\n", errmsg );
    ldap_memfree( errmsg );
  }
  return( 1 );
}
...

In the preceding example, the client prints out this error message if it does not have access permissions to delete the entry:

ldap_delete_s: Insufficient access

Calling Synchronous and Asynchronous Functions

You can perform the operation as a synchronous or asynchronous operation. For example, to search the directory, you can call either the synchronous function ldap_search_ext_s(), or the asynchronous function ldap_search_ext(). In general, the synchronous functions have names ending with the characters _s (for example, ldap_search_ext_s()).

Calling Synchronous Functions

When you call a synchronous function, your client waits for the operation to complete before executing any subsequent lines of code. Synchronous functions return LDAP_SUCCESS if successful and an LDAP error code if not successful.

This example calls a synchronous function to delete an entry in the directory.

Code Example 5-7 - Calling synchronous functions

#include <stdio.h>
#include "ldap.h"
...
LDAP      *ld;
char      *matched_msg = NULL, *error_msg = NULL;
int        rc;
...
/* Perform an LDAP delete operation. */
rc = ldap_delete_ext_s( ld, DELETE_DN, NULL, NULL );
if ( rc != LDAP_SUCCESS ) {
  fprintf( stderr, "ldap_delete_ext_s: %s\n", ldap_err2string( rc ) );
  ldap_get_lderrno( ld, &matched_msg, &error_msg );
  if ( error_msg != NULL && *error_msg != '\0' ) {
    fprintf( stderr, "%s\n", error_msg );
  }

  /* If the server cannot find an entry with the specified DN, 
    it may send back the portion of the DN that matches 
    an existing entry, For details, see 
    "Receiving the Portion of the DN Matching an Entry" */
  if ( matched_msg != NULL && *matched_msg != '\0' ) {
    fprintf( stderr, 
      "Part of the DN that matches an existing entry: %s\n", 
      matched_msg );
  }
} else {
  printf( "%s deleted successfully.\n", DELETE_DN );
}
...

To see additional sample programs that call synchronous functions, see the source files in the examples directory of the Mozilla LDAP C SDK.

Calling Asynchronous Functions

When you call an asynchronous function, your client does not need to wait for the operation to complete. The client can continue performing other tasks (such as initiating other LDAP operations) while the LDAP operation is executing.

An asynchronous function passes back a unique message ID that identifies the operation being performed. You can pass this message ID to the ldap_result() function to check the status of the LDAP operation.

This section explains how to call an asynchronous function and check the results of the LDAP operation. The following topics are covered:

The section also includes an example of calling an asynchronous function (see "Example of Calling an Asynchronous Function").

To see sample programs that call asynchronous functions see the source files in the examples directory of the Mozilla LDAP C SDK.

Checking if the LDAP Request was Sent

Asynchronous functions return an LDAP result code indicating whether or not the LDAP request was successfully sent to the server. If the function returns LDAP_SUCCESS, the function successfully sent the request to the server.

For example, the following section of code send an LDAP delete request to the server. The example checks if the result was successfully sent.

Code Example 5-8 - Asynchronous return codes

#include <stdio.h>
#include "ldap.h"
...
/* Change these as needed. */
#define DELETE_DN "uid=wjensen,ou=People,dc=example,dc=com"
...
LDAP       *ld;
int        rc, msgid;
...
/* Send an LDAP delete request to the server. */
rc = ldap_delete_ext( ld, DELETE_DN, NULL, NULL, &msgid );
if ( rc != LDAP_SUCCESS ) {
  /* If the request was not sent successfully, 
    print an error message and return. */
  fprintf( stderr, "ldap_delete_ext: %s\n", ldap_err2string( rc ) );
  ldap_unbind( ld );
  return( 1 );
}
...

Getting the Server Response

If the request was successfully sent, the function passes back the message ID of the LDAP operation. You can use this message ID to determine if the server has sent back the results for this specific operation.

To check for results, call the ldap_result() function, and pass the message ID as a parameter. You can also specify a time-out period to wait for results from the server.

The function returns one of the following values:

  • -1 indicates that an error occurred.
  • 0 indicates that the time-out period has been exceeded and the server has not yet sent a response back to your client.
  • Any other value indicates that the server has sent a response for the requested operation back to your client.

You can set up a loop to poll for results while doing other work. For example, suppose you defined a function that did work while waiting for the LDAP operation to complete and the server to send a response back to your client:

int global_counter = 0;
void
do_other_work()
{
  global_counter++;
}

You can set up a while loop to call your function when you are not checking for the server's response:

Code Example 5-9 - Getting response from a server

#include <stdio.h>
#include "ldap.h"
...
LDAP              *ld;
LDAPMessage       *res;
LDAPControl       **serverctrls;
char              *matched_msg = NULL, *error_msg = NULL;
char              **referrals;
int               rc, parse_rc, msgid, finished = 0;
struct timeval    zerotime;

zerotime.tv_sec = zerotime.tv_usec = 0L;
...
/* Send an LDAP delete request to the server. */
rc = ldap_delete_ext( ld, DELETE_DN, NULL, NULL, &msgid );
...
/* Poll the server for the results of the LDAP operation. */
while ( !finished ) {
  rc = ldap_result( ld, msgid, 0, &zerotime, &res );

  /* Check to see if a result was received. */
  switch ( rc ) {
  case -1:
    .../* An error occurred. */...
  case 0:
    /* The timeout period specified by zerotime was exceeded.
      This means that the server has still not yet sent the 
      results of the delete operation back to your client.
      Break out of this switch statement, and continue calling 
      ldap_result() to poll for results. */
  default:
    finished = 1;
    .../* Your client received a response from the server. */...
  }

  /* Do other work while waiting. This is 
    called if ldap_result() returns 0 (before you continue 
    to the top of the loop and call ldap_result() again). */
  if ( !finished ) {
    do_other_work();
  }
  ...
}
...

Getting Information from the Server Response

If the ldap_result() function gets the response sent from the server, the result parameter passes back a pointer to an LDAPMessage structure. This structure contains the server's response, which can include the following information:

  • An LDAP result code specifying the result of the operation you requested.
  • An additional error message (optional) sent back from the server.
  • If the server was not able to find the entry specified by a DN, the portion of the DN that identifies an existing entry (see "Receiving the Portion of the DN Matching an Entry" for details).
  • A set of referrals, if the server's directory does not contain the requested entries and if the server is configured to refer clients to other servers.
  • A set of server response controls applicable to the operation you requested (see Chapter 14 - Working with LDAP Controls for information on LDAPv3 controls).

Note that when processing LDAP search operations, the server can also send back individual entries matching the search, individual search references, and chains of entries and search references. For information on processing these types of results from the server, see Chapter 6 - Searching the Directory.

To get information from a server response, call the ldap_parse_result() function:

LDAP_API(int) LDAP_CALL
ldap_parse_result( LDAP *ld, LDAPMessage *res, int *errcodep,
  char **matcheddnp, char **errmsgp, char ***referralsp,
  LDAPControl ***serverctrlsp, int freeit );

You can get the following information from parameters of this function:

  • errcodep is the LDAP result code of the operation that the server finished processing.
  • matcheddnp is the portion of the DN that matches an existing entry, if the server is not able to find an entry for a DN that you've specified (for details, see "Receiving the Portion of the DN Matching an Entry").
  • errmsgp is an additional error message that the server can send to your client.
  • referralsp is a set of referrals sent back to your client by the server, if you've requested an entry that is not part of the directory tree managed by the server and if the server is configured to refer clients to other LDAP servers.
  • serverctrlsp is a set of server response controls applicable to the LDAP operation.

When you are done, you should call ldap_msgfree() to free the LDAPMessage structure unless the structure is part of a chain of results. If you pass a non-zero value for the freeit parameter, the structure is automatically freed after the information is retrieved.

The result code returned by this function is not the same as the result code of the operation (errcodep). The result code returned by this operation indicates the success or failure of parsing the LDAPMessage structure.

For example, the following section of code retrieves error information from an LDAPMessage structure returned by the ldap_result().

Code Example 5-10 - Regiving error information from an LDAPMessage structure

#include <stdio.h>
#include "ldap.h"
...
LDAP              *ld;
LDAPMessage       *res;
LDAPControl       **serverctrls;
char              *matched_msg = NULL, *error_msg = NULL;
char              **referrals;
int               rc, parse_rc, msgid, finished = 0;
struct timeval    zerotime;

zerotime.tv_sec = zerotime.tv_usec = 0L;
...
rc = ldap_result( ld, msgid, 0, &zerotime, &res );

/* Check to see if a result was received. */
switch ( rc ) {
case -1:
  ...
case 0:
  ...
default:
  ...
  /* Call ldap_parse_result() to get information from the results 
    received from the server. */
  parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg, 
    &error_msg, &referrals, &serverctrls, 1 );

  /* Make sure the results were parsed successfully. */
  if ( parse_rc != LDAP_SUCCESS ) {
    fprintf( stderr, "ldap_parse_result: %s\n", 
      ldap_err2string( parse_rc ) );
    ldap_unbind( ld );
    return( 1 );
  }

  /* Check the results of the LDAP operation. */
  if ( rc != LDAP_SUCCESS ) {
    fprintf(stderr, "Error: %s\n", ldap_err2string(rc));
    if ( error_msg != NULL & *error_msg != '\0' ) {
      fprintf( stderr, "%s\n", error_msg );
    }
    /* If the server returned the portion of the DN 
      that identifies an existing entry, 
      print it out. (For details, see 
      "Receiving the Portion of the DN Matching an Entry") */
    if ( matched_msg != NULL && *matched_msg != '\0' ) {
      fprintf( stderr, 
        "Part of the DN that matches an existing entry: %s\n", 
        matched_msg );
    }
  } else {
    printf( "Operation completed successfully" );
  }
}
...

Canceling an Operation in Progress

If you need to cancel the LDAP operation, call the ldap_abandon_ext() function. The function returns LDAP_SUCCESS if successful or an LDAP result code if an error occurs.

Once you cancel an LDAP operation, you cannot retrieve the results of that operation. (In other words, calling ldap_result() does not return any results.)

Example of Calling an Asynchronous Function

The following section of code calls an asynchronous function to delete an entry in the directory. The code calls ldap_result() within a loop to poll the results of the LDAP delete operation.

Code Example 5-11 - Deleting an entry using an asynchronous function call

#include <stdio.h>
#include "ldap.h"
...
void do_other_work();
int global_counter = 0;
...
/* Change these as needed. */
#define DELETE_DN "uid=wjensen,ou=People,dc=example,dc=com"
...
LDAP              *ld;
LDAPMessage       *res;
LDAPControl       **serverctrls;
char              *matched_msg = NULL, *error_msg = NULL;
char              **referrals;
int               rc, parse_rc, msgid, finished = 0;
struct timeval    zerotime;

zerotime.tv_sec = zerotime.tv_usec = 0L;
...
/* Send an LDAP delete request to the server. */
rc = ldap_delete_ext( ld, DELETE_DN, NULL, NULL, &msgid );
if ( rc != LDAP_SUCCESS ) {
  fprintf( stderr, "ldap_delete_ext: %s\n", ldap_err2string( rc ) );
  ldap_unbind( ld );
  return( 1 );
}

/* Poll the server for the results of the delete operation. */
while ( !finished ) {
  /* Call ldap_result() to get the results of the delete operation. 
    ldap_result() blocks for the time period 
    specified by the timeout argument (set to 
    zerotime here) while waiting for the result 
    from the server. */
  rc = ldap_result( ld, msgid, 0, &zerotime, &res );

  /* Check to see if a result was received. */
  switch ( rc ) {
  case -1:
    /* If ldap_result() returned -1, an error occurred. */
    rc = ldap_get_lderrno( ld, NULL, NULL );
    fprintf( stderr, "ldap_result: %s\n", ldap_err2string( rc ) );
    ldap_unbind( ld );
    return( 1 );

  case 0:
    /* The timeout period specified by zerotime was exceeded.
      This means that the server has still not yet sent the 
      results of the delete operation back to your client.
      Break out of this switch statement, and continue calling 
      ldap_result() to poll for results. */
    break;

  default:
    /* ldap_result() got the results of the delete operation
      from the server. No need to keep polling. */
    finished = 1;

    /* Call ldap_parse_result() to get information from the results 
      received from the server. Note the last
      argument is a non-zero value. This means after the 
      function retrieves information from the 
      LDAPMessage structure , the structure is freed. 
      (You don't need to call ldap_msgfree() to free the structure.)
    */
    parse_rc = ldap_parse_result( ld, res, &rc, &matched_msg, 
      &error_msg, &referrals, &serverctrls, 1 );
    if ( parse_rc != LDAP_SUCCESS ) {
      fprintf( stderr, "ldap_parse_result: %s\n", 
        ldap_err2string( parse_rc ) );
      ldap_unbind( ld );
      return( 1 );
    }

    /* Check the results of the LDAP delete operation. */
    if ( rc != LDAP_SUCCESS ) {
      fprintf(stderr, "ldap_delete_ext: %s\n", ldap_err2string(rc));
      if ( error_msg != NULL & *error_msg != '\0' ) {
        fprintf( stderr, "%s\n", error_msg );
      }
      /* Print the portion of a specified DN 
        that matches an existing entry, if 
        returned by the server. (For details, see 
        "Receiving the Portion of the DN Matching an Entry.") */
      if ( matched_msg != NULL && *matched_msg != '\0' ) {
        fprintf( stderr, 
          "Part of the DN that matches an existing entry: %s\n", 
          matched_msg );
      }
    } else {
      printf( "%s deleted successfully.\n"
        "Counted to %d while waiting for the delete operation.\n",
        DELETE_DN, global_counter );
    }
  }

  /* Do other work while waiting for the results of the 
    delete operation. */
  if ( !finished ) {
    do_other_work();
  }
}
ldap_unbind( ld );
return 0;
...

/* Perform other work while polling for results. */
void
do_other_work()
{
  global_counter++;
}
...

Handling Referrals

If an LDAP server receives a request for a DN that is not under its directory tree, it can refer clients to another LDAP server that may contain that DN. This is known as a referral.

This section explains how to set up your LDAP client to handle referrals automatically. The following topics are covered:

Understanding Referrals

Suppose an LDAP server has a directory that starts under "dc=example,dc=com". If your client sends the server a request to modify the entry with the DN "uid=bjensen,ou=People,o=Example,c=US" (an entry that is not under "dc=example,dc=com"), one of the following may occur:

  • If the server is not configured to send a referral, the server sends back an LDAP_NO_SUCH_OBJECT result code.
  • If the server is configured to refer you to another LDAP server, the server sends a referral back to your client. This consists of the result code (LDAP_PARTIAL_RESULTS for LDAPv2 clients, LDAP_REFERRAL for LDAPv3 clients) and one or more LDAP URLs.

    For LDAPv2 clients, the URLs are included in the error message that the server sends to the client. For LDAPv3 clients, the URLs are included in a separate section of the result.

    Depending on how your LDAP client is configured, one of the following may occur:
    • If your client handles referrals automatically, your client connects to the LDAP server specified in the referral and requests the operation from that server. (The client binds anonymously to that server. To bind as a specific user, see the section "Binding When Following Referrals.")
    • If your client does not handle referrals automatically, your client returns the result code sent from the server (LDAP_PARTIAL_RESULTS or LDAP_REFERRAL). You can get the LDAP URLs from the result by calling the ldap_parse_result() function.
    By default, clients built with the Mozilla LDAP C SDK are configured to follow referrals automatically.

Another concept similar to a referral is a search reference. A search reference is an entry with the object class "referral". The "ref" attribute of this object contains an LDAP URL that identifies another LDAP server.

When your client searches a subtree of the directory that contains search references, the server returns a mix of matching entries and search references. As your client retrieves search references from the server, one of the following occurs:

  • If your client handles referrals automatically, the LDAP API library retrieves each search reference, binds to the server identified in the reference (see "Binding When Following Referrals" for information on specifying the DN and password for binding), and retrieves the entry.
  • If your client does not handle referrals automatically, the LDAP API library simply adds the search reference to the chain of search results. The search reference is a message of the type LDAP_RES_SEARCH_REFERENCE.

    You can get the search references from a chain of results by calling the ldap_first_reference() and ldap_next_reference() functions. You can also call the ldap_first_message() and ldap_next_message() functions to get each message in the search results, then call the ldap_msgtype() function to determine if the message is of the type LDAP_RES_SEARCH_REFERENCE.

Enabling or Disabling Referral Handling

By default, Mozilla LDAP C SDK clients automatically follow these referrals to other servers.

To change the way referrals are handled, call the ldap_set_option() function and pass LDAP_OPT_REFERRALS as the value of the option parameter.

  • To prevent the client from automatically following referrals, set the optdata parameter to LDAP_OPT_OFF.
  • If you want the client to automatically follow referrals again, set the optdata parameter to LDAP_OPT_ON.

Note that both LDAP_OPT_OFF and LDAP_OPT_ON are cast to (void *). You can pass these parameters directly to the function (see the example below).

The following example prevents the client from automatically following referrals to other LDAP servers.

Code Example 5-12 - Disabling referrals

#include <stdio.h>
#include "ldap.h"
...
LDAP    *ld;
int     rc;
char    *host = "localhost";
...
/* Initialize a session with the LDAP server ldap.example.com:389. */
if ( ( ld = ldap_init( host, LDAP_PORT ) ) == NULL ) { 
  perror( "ldap_init" );
  return( 1 );
}

/* Never follow referrals. */
if (ldap_set_option(ld,LDAP_OPT_REFERRALS,LDAP_OPT_OFF)!=LDAP_SUCCESS){
  rc = ldap_get_lderrno( ld, NULL, NULL );
  fprintf( stderr, "ldap_set_option: %s\n", 
    ldap_err2string( rc );
  return( 1 );
}
...

Limiting Referral Hops

As a preference for the connection (or as a search constraint for specific search operations), you can specify the maximum number of referral hops that should be followed in a sequence of referrals. This is called the referral hop limit.

For example, suppose you set a limit of 2 referral hops. If your client is referred from LDAP server A to LDAP server B, from LDAP server B to LDAP server C, and from LDAP server C to LDAP server D, your client is being referred 3 times in a row, and it will not follow the referral to LDAP server D because this exceeds the referral hop limit.

If the referral hop limit is exceeded, the LDAP result code LDAP_REFERRAL_LIMIT_EXCEEDED is returned.

To set this limit, pass LDAP_OPT_REFERRAL_HOP_LIMIT as the value of the option parameter and pass the maximum number of hops as value of the optdata parameter.

By default, the maximum number of hops is 5.

Binding When Following Referrals

If the session is set up so that referrals are always followed (see "Enabling or Disabling Referral Handling" for more information), the LDAP server that you connect to may refer you to another LDAP server. By default, the client binds anonymously (no user name or password specified) when following referrals.

This section explains how to set up your client to authenticate with a selected DN and credentials when following referrals. Topics include:

How Binding Works When Following Referrals

If you want your client to authenticate to the LDAP server that it is referred to, you need to specify a way to get the DN and password to be used for authentication. You need to define a rebind function of the type LDAP_REBINDPROC_CALLBACK. Then, you specify that your function should be used if binding to other servers when following referrals.

The following steps explain how this works:

  1. The LDAP server sends a referral back to the client. The referral contains an LDAP URL that identifies another LDAP server.
  2. The client calls the rebind function (the function specified by the LDAP_OPT_REBIND_FN option), passing 0 as the freeit argument.
  3. The rebind function sets the dnp, passwdp, and authmethodp arguments to point to the following information:
    • The dnp argument is set to point to the DN to be used to authenticate to the new LDAP server.
    • The passwdp argument is set to point to the credentials for this DN.
    • The authmethodp argument is set to point to the method of authentication used (for example, LDAP_AUTH_SIMPLE).
  4. If successful, the rebind function returns LDAP_SUCCESS, and referral processing continues.

    (If any other value is returned, referral processing stops, and that value is returned as the result code for the original LDAP request.)
  5. The client gets the DN, credentials, and authentication method from the arguments of the rebind function and uses this information to authenticate to the new LDAP server.
  6. The client calls the rebind function again, passing 1 as the freeit argument.
  7. The rebind function frees any memory allocated earlier to specify the DN and credentials.

Defining the Rebind Function

You need to define a rebind function that does the following:

  • If freeit is 0, set the following pointers:
    • Set dnp to point to the DN to be used for authentication.
    • Set passwdp to point to the credentials to be used for authentication.
    • Set authmethodp to point to the method of authentication used (for example, LDAP_AUTH_SIMPLE).

    You can make use of the arg argument, which is a pointer to the argument specified in the ldap_set_rebind_proc() function.

    If successful, return LDAP_SUCCESS; otherwise, return the appropriate LDAP error code.
  • If freeit is 1, free any memory that you allocated to create the DN and credentials.

You need to write a function that has the following prototype:

int LDAP_CALL LDAP_CALLBACK rebindproc( LDAP *ld, char **dnp,
  char **passwdp, int *authmethodp, int freeit, void *arg );

The parameters for this prototype are described below:

Table 5-2 - LDAP_CALL_LDAP_CALLBACK parameters

Parameter Name Description
ld The connection handle to the LDAP server.
dnp A pointer to the distinguished name of the user (or entity) who wants to perform the LDAP operations. Your function needs to set this value.
passwdp A pointer to the user's (or entity's) password. Your function needs to set this value.
authmethodp A pointer to the method of authentication. Your function needs to set this value.
freeit Specifies whether or not to free the memory allocated by the previous rebindproc() function call (in the event that this function is called more than once). If freeit is set to a non-zero value, your function should free the memory allocated by the previous call.
arg A pointer to data that can be passed to your function.

LDAP_CALL and LDAP_CALLBACK are used to set up calling conventions (for example, Pascal calling conventions on Windows). These are defined in the lber.h header file.

Registering the Rebind Function

Once you have a function that follows this prototype, you need to register the rebind function. You can do this in one of the following two ways:

  • Call the LDAP API function ldap_set_rebind_proc(), specifying your function and any data that you want passed as an argument.
  • Call ldap_set_option() to set the LDAP_OPT_REBIND_FN option to your function and the LDAP_OPT_REBIND_ARG option to specify any arguments you want passed to your rebind function.

Both of these methods register your rebind function.

Setting Up an In-Memory Cache

The Mozilla LDAP C SDK includes functions that allow you to create an in-memory cache of search results for your client. When send a search request and receive results, the results are cached. The next time your client issues the same search request, the results are read from the cache.

To set up a cache for your connection, do the following:

  1. Call the ldap_memcache_init() function to create a new LDAPMemCache structure, which is the cache. Pass the pointer to this structure to subsequent operations.
  2. Call the ldap_memcache_set() function to associate the cache with an LDAP connection handle (an LDAP structure).

For example:

Code Example 5-13 - Creating an in-memory cache

#include "ldap.h"
...
#define HOSTNAME "localhost"
#define PORTNUMBER LDAP_PORT
...
LDAP          *ld;
LDAPMemCache  *dircache;
char *matched_msg = NULL, *error_msg = NULL;
int rc;
...
/* Get a handle to an LDAP connection. */
if ( (ld = ldap_init( HOSTNAME, PORTNUMBER )) == NULL ) {
  perror( "ldap_init" );
  return( 1 );
}
...
/* Create an in-memory cache. */
rc = ldap_memcache_init( 0, 0, NULL, NULL, &dircache );
if ( rc != LDAP_SUCCESS ) {
  fprintf( stderr, "ldap_memcache_init: %s\n", ldap_err2string( rc ) );
  ldap_unbind( ld );
  return( 1 );
}

/* Associate the cache with the connection. */
rc = ldap_memcache_set( ld, dircache );
if ( rc != LDAP_SUCCESS ) {
  fprintf( stderr, "ldap_memcache_set: %s\n", ldap_err2string( rc ) );
  ldap_unbind( ld );
  return( 1 );
}
...

When a search request is cached, the search criteria is used as the key to the item in the cache. If you run the same search again, the results are read from the cache. If you alter the criteria (for example, specifying that you want all attributes returned instead of just the uid attribute), your client gets the results from the server rather than from the cache.

The cache periodically checks for expired items and removes them from the cache. If you are writing a multi threaded application and want to set up a separate thread to keep the cache up-to-date, you can call the ldap_memcache_update() function.

To remove items from the cache or flush the cache, call the ldap_memcache_flush() function. When you are done working with the cache, call the ldap_memcache_destroy() function.

Handling Failover

While performing an LDAP operation, if the LDAP client loses the connection with the server, the LDAP API library returns an LDAP_SERVER_DOWN or LDAP_CONNECT_ERROR result code.

To reconnect to the server, you can do one of the following:

  • Free the current connection handle and create a new connection handle.

    For details, see "Creating a New Connection Handle."
  • Use the reconnect option (LDAP_OPT_RECONNECT) to connect to the server again with the same connection handle.

    You can use this option if you do not want to free the connection handle (for example, if multiple threads are sharing the same connection handle). For details, see "Using the Reconnect Option."

Creating a New Connection Handle

Call the ldap_unbind() or ldap_unbind_s() function to free the existing connection handle (the LDAP structure), and then call the ldap_init() function to open and initialize a new connection. For example:

Code Example 5-14 - Initializing a new connection handle

...
LDAP *ld;
int tries = 0, rc = 0;
...
do {
  /* Call a function that performs an LDAP operation 
  (my_ldap_request_function() can be any of these functions, 
  such as ldap_search_ext_s()) */
  rc = my_ldap_request_function( ld ); 

  /* Check to see if the connection was lost. */
  if ( rc != LDAP_SERVER_DOWN && rc != LDAP_CONNECT_ERROR ) {
    return( rc ); /* Return result code. */
  }

  /* If the connection was lost, free the handle. */
  ldap_unbind( ld );

  /* Create a new connection handle and 
   attempt to bind again. */
  if (( ld = ldap_init( hostlist, port )) != NULL ) {
    ldap_simple_bind_s();

    /* Perform any other initialization 
    work on the connection handle. */
  }
} while ( ld != NULL && ++tries < 2 );
...

The disadvantage of this approach is that you need free the LDAP connection handle, which can make it difficult to share connection handles between threads. If you do not want to free the connection handle, you can use the reconnect option instead, as described in "Using the Reconnect Option."

Using the Reconnect Option

To reconnect to the server without freeing the connection handle (for example, if multiple threads need to share the same connection handle), call the ldap_set_option() function to set the LDAP_OPT_RECONNECT option to < code>LDAP_OPT_ON. Do this right after calling ldap_init():

Code Example 5-15 - Reconnecting to an existing connection handle

...
#define HOSTNAME "localhost"
#define PORTNUMBER LDAP_PORT
...
LDAP *ld;
...
/* Get a handle to an LDAP connection. */
if ( (ld = ldap_init( HOSTNAME, PORTNUMBER )) == NULL ) {
  perror( "ldap_init" );
  return( 1 );
}

/* Set the reconnect option. */
if ( ldap_set_option( ld, LDAP_OPT_RECONNECT, LDAP_OPT_ON ) == 0 ) {
  /* success */
  } else { 
  /* failure */
} 
...

After setting this option, if the connection to the LDAP server has been lost (if the LDAP API library returns an LDAP_SERVER_DOWN or LDAP_CONNECT_ERROR result code to your client), call the ldap_simple_bind_s() function to reestablish a connection to one of the hosts specified in the ldap_init() function call.

If your client is able to reconnect with the server, the ldap_simple_bind_s() function call issues an LDAP bind request to the server and returns the result.

Note that if the client has been successfully authenticated to the server using the current LDAP structure and if the connection to the LDAP server has not been lost, ldap_simple_bind_s() returns LDAP_SUCCESS without contacting the LDAP server.

The function is designed to do this in cases where more than one thread shares the same LDAP connection handle and receives an LDAP_SERVER_DOWN or LDAP_CONNECT_ERROR error. If multiple threads call the ldap_simple_bind_s() function, the function is designed so that only one of the threads actually issues the bind operation. For the other threads, the function returns LDAP_SUCCESS without contacting the LDAP server.

The important side effect to note is that if the LDAP_OPT_RECONNECT option is set, ldap_simple_bind_s() may return LDAP_SUCCESS without contacting the LDAP server. (The function only returns this if a bind with the DN was successfully performed in the past.)

The following section of code attempts to reconnect to the server if the client is disconnected from the server:

Code Example 5-16 - Reconnecting to a server after a disconnect

...
LDAP *ld;
int tries = 0, rc = 0;
...
do {
  /* Call a function that performs an LDAP operation 
  (my_ldap_request_function() can be any of these functions, 
  such as ldap_search_ext_s()) */
  rc = my_ldap_request_function( ld ); 

  /* Check to see if the connection was lost. */
  if ( rc != LDAP_SERVER_DOWN && rc != LDAP_CONNECT_ERROR ) {
    return( rc ); /* Return the result code. */
  }

  /* If the connection was lost, call 
  ldap_simple_bind_s() to reconnect. */
  if ( ldap_simple_bind_s( ld, dn, passwd ) != LDAP_SUCCESS ) {
    /* failure -- could not reconnect */
    /* remember that ld as bad */
    return( rc );
  }
} while ( ++tries < 2 );