Mozilla LDAP SDK Programmer's Guide/Using the LDAP C API

From MozillaWiki
Jump to: navigation, search

This section covers features of the LDAP C API to use when writing an LDAP client application with LDAP C SDK.

Synchronous and Asynchronous Functions

You can perform most operations with synchronous or with asynchronous functions. For example, to search the directory, you can call either the synchronous ldap_search_ext_s() function or the asynchronous ldap_search_ext() function. In general, all synchronous functions have names that end with _s.

The difference between the synchronous and asynchronous functions is the calling convention. The LDAP exchanges are identical.


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 when they are successful. Synchronous functions return an LDAP error code when they are not successful. The following example deletes the entry in the directory.

#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.*/
  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 other sample programs that call synchronous functions, view the source files in the examples/ directory.


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 asynchronous operation is executing. An asynchronous function passes back a unique message ID to identify the operation being performed. You can pass this message ID to the ldap_result() function to check the status of the operation. The following sections explain how to call an asynchronous function and how to check the results of the operation. To see other sample programs that call asynchronous functions, view the source files in the examples/ directory.


Verifying that an 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 has successfully sent the request to the server. The following example sends an LDAP delete request to the server and checks if the result was sent successfully. The example uses the asynchronous ldap_delete_ext() function.

#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 );
}
...

Retrieving the Server Response


If the request was sent successfully, the function passes the message ID of the LDAP operation back to the client. Use the message ID to determine if the server has sent back results for this operation. Call ldap_result(), passing the message ID as a parameter.

  • -1 indicates that an error occurred.
  • 0 indicates that the timeout period has been exceeded and that 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. The ldap_result() parameter passes back a pointer to an LDAPMessage structure.
    • An LDAP result code that specifies the result of the operation you requested
    • An additional error message sent back from the server
      This information is optional.
    • If the server cannot find the entry specified by a DN, the portion that identifies an existing entry.
    • A set of referrals, if the servers directory does not contain the requested entries
      The server must be configured to return referrals.
    • A set of server response controls that apply to the operation you requested

You can specify a timeout period to wait for results from the server.

Polling Loop

You can set up a loop to poll for results while doing other work. The following example defines a function that does other work while waiting for the server to send a response back to your client.

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

while Loop

This example sets up a while() loop to call your function when you are not checking for the servers response.

#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, meaning
       the server has still not yet sent the results of the delete
       operation back to the 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 top of the loop and call
     ldap_result() again). */
  if ( !finished ) {
    do_other_work();
  }
  ...
}
...

Getting Information From a Server Response


To get information from the server response, call ldap_parse_result() as shown here.

LDAP_API(int) LDAP_CALL
ldap_parse_result( LDAP *ld, LDAPMessage *res, int *errcodep,
  char **matcheddnp, char **errmsgp, char ***referralsp,
  LDAPControl ***serverctrlsp, int freeit );
  • errcodep holds the LDAP result code of the operation that the server finished processing.
  • errmsgp is an additional error message that the server can send to your client.
  • matcheddnp is the portion of the DN that matches an existing entry. The portion of the DN is used when the server is not able to find an entry for the DN that you specified.
  • referralsp is a set of referrals sent back to your client. The set of referrals is sent if you requested an entry that is not part of the DIT managed by the server. The server must be configured to refer clients to other LDAP servers.
  • serverctrlsp is a set of server response controls that apply to the operation.

When processing LDAP search operations, the server can send back individual entries, individual search references, chains of entries, and chains of search references.

The following example retrieves error information from an LDAPMessage structure returned by ldap_result().

#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. */
    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" );
  }
}
...

Freeing an LDAPMessage Structure


When you are done, call ldap_msgfree() to free the LDAPMessage structure unless the structure is part of a chain of results. The result code returned by this function is not the same as the result code of the operation, errcodep. This operation returns a result code that indicates the type of operation to which the freed LDAPMessage structure is a response.

If you pass a non-zero value for the freeit parameter of ldap_result(), the structure is automatically freed after the information is retrieved.


Canceling an Operation in Progress


If you need to cancel an LDAP operation, call ldap_abandon_ext(). The function returns LDAP_SUCCESS if successful, or an LDAP result code if an error occurs. After you cancel an operation, you cannot retrieve the results of that operation. Thus, calling ldap_result() does not return any results.


Sample Code to Call an Asynchronous Function


The following example calls ldap_delete_ext() to delete an entry in the directory, and ldap_result() within a loop to poll the results of the delete.

#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 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, meaning
       the server has still not yet sent the results of the delete
       operation back to the 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 do not 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. */
      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++;
}
...

Retrieving SDK Information

You can get information about the particular version of LDAP C SDK that you are using by calling the ldap_get_option() function:

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

The retrieved information can include the version of the SDK or the highest version of the LDAP that the SDK supports. This example shows how to use this function to retrieve 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 SDK functions allocate memory when called. When you have finished working with data allocated by these functions, you should free the memory. The following shows some of the functions that allocate memory and the corresponding functions you use to free the memory when done.

Functions to Allocate and Free Memory
Function to Free Memory
Type of Memory Freed
ldap_unbind(), ldap_unbind_s()
Frees LDAP structures allocated by ldap_init() or prldap_init().
ldap_msgfree()
Frees LDAPMessage structures allocated by ldap_result() or ldap_search_ext_s().
ldap_ber_free()
Frees BerElement structures allocated by ldap_first_attribute().
ldap_value_free()
Frees char ** arrays and structures allocated by ldap_get_values().
ldap_value_free_len()
Frees arrays of berval structures allocated by ldap_get_values_len().
ber_bvfree()
Frees berval structures allocated by 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 ldap_friendly_name().
ldap_free_urldesc()
Frees LDAPURLDesc structures allocated by ldap_url_parse().
ldap_getfilter_free()
Frees LDAPFiltDesc structures allocated by 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 that you allocate by calling ldap_create_sort_keylist().
ldap_control_free()
Frees LDAPControl structures that you allocate by calling ldap_create_sort_control() or ldap_create_persistentsearch_control().
ldap_controls_free()
Frees LDAPControl** arrays and structures that you allocate by calling ldap_get_entry_controls(), ldap_parse_result(), or ldap_parse_reference().
ldap_memfree()
Frees any other types of memory that you allocate. This function is a general function for freeing memory.

Reporting Errors

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


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. This example sets the LDAP_PARAM_ERROR error code in an LDAP structure.

#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 );
}
...

Getting Information About an Error


When an error occurs in an LDAP operation, the server sends an LDAP result code for the error back to the client. The server also sends a message with any additional information about the error.

  • If you are calling asynchronous functions, you can get the information from the LDAPMessage structure that represents the result the server returns.
  • Sometimes, you do not have an LDAPMessage structure. For example, you might be calling functions that do not interact with the server. When you do not have a message ID, get error information from the LDAP connection handle.


Getting Information From an LDAPMessage Structure


If you have requested the operation through an asynchronous function, get the result 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 whose prototype is shown here.

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 LDAP result code is the errcodep argument.
  • Additional information from the server is passed back as the errmsgp argument.
  • When the server cannot find an entry from a DN, the portion of the DN that identifies an entry is passed as the matcheddnp argument.

You can also get the error message that describes the LDAP result code by using the ldap_err2string() function.

The following example gets and prints information about an error returned from the server.

#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. */
      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 Information From an LDAP Structure


Sometimes, you do not get an LDAPMessage structure. For example, when you call functions that do not interact with the server, get error information from the connection handle. You can get information about the last error that has occurred by calling the ldap_get_lerrno() function whose prototype is shown here.

LDAP_API(int) LDAP_CALL ldap_get_lderrno(LDAP *ld,
  char **m, char **s);
  • The LDAP result code is returned by this function.
  • Additional information from the server is passed back as the s argument.
  • When the server cannot find an entry from a DN, the portion of the DN that identifies an entry is passed as the m argument.

If you do not need to use the parameters returned by the ldap_get_lerrno function, set the parameters to NULL:

ldap_get_lderrno( ld, NULL, NULL );

The following example gets and prints information about an error 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. */
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 From an Error Code


If you have an error code, you can retrieve its corresponding error message using the ldap_err2string function. The function returns a pointer to the error message. The pointer returned by this function is a pointer to static data. Do not free this string.

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

Receiving the Matching Portion of a DN


If the server cannot find an entry specified by a DN, the server can return the portion of the DN that identifies an existing entry. According to LDAP v3, 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 the DN that matches an entry that is closest to the requested entry.

For example, suppose that 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 ou=Contractors,dc=example,dc=com does exist, the server sends this portion of the DN with the result code LDAP_NO_SUCH_OBJECT. If ou=Contractors,dc=example,dc=com does not exist, but dc=example,dc=com does, the server sends dc=example,dc=com back to the client. The server also returns the result code LDAP_NO_SUCH_OBJECT. Basically, the server removes one DN component at a time, until the server can find a DN that identifies an existing entry.


Printing Error Messages


To print the error message that describes the last error that occurred, call the ldap_get_lerrno() function. The following prints a message if a function fails to delete an entry in the server.

#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 );
}
...

The client also prints the following message if the client does not have access permissions to delete the entry:

ldap_delete_s: Insufficient access

Handling Referrals With LDAP C SDK

When a server receives a request for a DN, that DN might not be in its directory tree. The server can refer clients to another server that might contain the DN. The reference that the client receives is called a referral.

Consider an LDAP server that has a directory that starts under dc=example,dc=com.

  • 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. The referral consists of the result code, LDAP_PARTIAL_RESULTS for LDAP v2 clients, or LDAP_REFERRAL for LDAP v3 clients, and one or more LDAP URLs. For LDAP v2 clients, the URLs are included in the error message that the server sends to the client. For LDAP v3 clients, the URLs are included in a separate section of the result.
  • If your client handles referrals automatically, the client connects to the LDAP server specified in the referral. The client then requests the operation from that server. The client binds anonymously to that server.
  • 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 LDAP C SDK follow referrals automatically.


Searching References and Referrals


A concept that is 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 class contains an LDAP URL that identifies another LDAP server. When your client searches a subtree of a directory that contains search references, the server returns a mix of matching entries and search references.

  • If your client handles referrals automatically, LDAP C SDK retrieves each search reference, binds to the server identified in the reference, and then retrieves the entry.
  • If your client does not handle referrals automatically, LDAP C SDK 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, and then call the ldap_msgtype() function to determine if the message is of the type LDAP_RES_SEARCH_REFERENCE.

See the ldap_ssl.h header file for information about specifying a DN and password for binding to a server for a referral.


Enabling or Disabling Referral Handling With LDAP C SDK


By default, clients built with LDAP C SDK automatically follow 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.

Both LDAP_OPT_OFF and LDAP_OPT_ON are cast to (void *). You can pass these parameters directly to the function as shown in the following example. The parameter prevents the client from automatically following referrals to other LDAP servers.

#include <stdio.h>
#include "ldap.h"
...
LDAP    *ld;
int     rc;
char    *host = "localhost";
...
/* Initialize a session with the LDAP server ldap.example.com:389. */
/* Use prldap_init() for IPv6 support. */
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 With LDAP C SDK


You can specify the maximum number of referral hops that should be followed in a sequence of referrals. The maximum setting is called the referral hop limit. You can specify the limit as a preference for the connection. You can also specify the limit as a search constraint for a specific search operation. For example, LDAP server 1 refers your client to server 2, server 2 to server 3, and then server 3 to server 4. Your client is being referred three times. With a limit of two referral hops, your client would not follow the referral to server 4, as the third referral exceeds the limit. If the referral hop limit is exceeded, the client returns the result code LDAP_REFERRAL_LIMIT_EXCEEDED.

To set the referral hop limit, pass LDAP_OPT_REFERRAL_HOP_LIMIT as the value of the option parameter. Also, pass the maximum number of hops as the value of the optdata parameter. By default, the maximum number of hops is 5.


Binding for Referrals


If the session setup specifies that the client always follows referrals, the LDAP server that the client connects to can refer the client to another server. By default, the client binds anonymously when following referrals. No user name or password is specified. The following sections explain how to set up your client to authenticate with a DN and with corresponding credentials.


How Referral Binding Works


To authenticate to the referred LDAP server, define a rebind function of the type LDAP_REBINDPROC_CALLBACK. The rebind function gets the DN and password to be used for authentication. Then, you specify that your function should be used if binding to other servers when following referrals.

  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, 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.
    • The dnp argument points to the DN used to authenticate to the new LDAP server.
    • The passwdp argument points to the credentials for this DN.
    • The authmethodp argument points to the method of authentication that is used, such as 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, the credentials, and the authentication method from the arguments of the rebind() function. The client 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 that was allocated earlier to specify the DN and credentials.


Defining the Rebind Function


  • If freeit is 0, do the following:
    • 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 to be used, such as LDAP_AUTH_SIMPLE.
    • Alternatively, you can also make use of the arg argument, a pointer to the argument specified in the ldap_set_rebind_proc() function. If successful, the function returns LDAP_SUCCESS. Otherwise, the function returns the appropriate LDAP error code.
  • If freeit is 1, free any memory that you allocated to create the DN and credentials.

The following code defines the rebind() function. The list defines the parameters of the rebind() function. LDAP_CALL and LDAP_CALLBACK set up calling conventions. The structures are defined in the lber.h header file.

int LDAP_CALL LDAP_CALLBACK rebindproc( LDAP *ld, char **dnp,
  char **passwdp, int *authmethodp, int freeit, void *arg );
  • ld: Pointer to the connection handle to the LDAP server.
  • dnp: Pointer to the DN of the user or entity who wants to perform the LDAP operations. Your function needs to set this value.
  • passwdp: Pointer to the user password. Your function needs to set this value.
  • authmethodp: 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 nonzero value, your function should free the memory allocated by the previous call.
  • arg: Pointer to data that can be passed to your function.


Registering the Rebind Function


  • Call 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. Use the LDAP_OPT_REBIND_ARG option to specify any arguments to pass to your rebind() function.

Creating an In-Memory Cache

LDAP C SDK includes functions that allow you to create an in-memory cache of search results for your client. Then, when sending a search request and receiving results, the results would be 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, complete the following procedure.

To Set Up an In-Memory Cache

  1. Call the ldap_memcache_init() function to create a new LDAPMemCache structure.
    The structure is the cache. Pass the pointer to this structure for subsequent operations.
  2. Call the ldap_memcache_set function() to associate the cache with an LDAP structure, which is a connection handle.
    When a search request is cached, the search criteria are 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, your client gets the results from the server rather than the cache. For example, you can specify to return all attributes instead of just the uid attribute.
    The cache periodically checks for expired items. The cache mechanism removes expired items from the cache.
  3. When you write a multithreaded application, set up a separate thread to keep the cache up to date.
    To keep the cache updated, call the ldap_memcache_update() function.
  4. If you want to remove items from the cache or flush the cache, call the ldap_memcache_flush() function.
  5. When you are done working with the cache, call the ldap_memcache_destroy() function.
#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. Use prldap_init() for IPv6. */
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 );
}
...

Handling Failover

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

  • Free the current connection handle. Then create 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.


Creating a New Connection Handle


Call the ldap_unbind() or ldap_unbind_s() function to free the existing connection handle, which is an LDAP structure. Then call ldap_init() or prldap_init() to initialize a new connection as shown in the following example.

The disadvantage of this approach is the need to free the connection handle, which can make sharing connection handles between threads difficult.

#include "ldap.h"
...
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. */
  /* Use prldap_init() for IPv6 support. */
  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 );
...

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 LDAP_OPT_ON. Call this function immediately after calling ldap_init() or prldap_init() as shown in the following example:

#include "ldap.h"
...
#define HOSTNAME "localhost"
#define PORTNUMBER LDAP_PORT
...
LDAP *ld;
...
/* Get a handle to an LDAP connection. Use prldap_init() for IPv6. */
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 */
}
...

If after setting this option, the connection to the server has been lost, the SDK returns an LDAP_CONNECT_ERROR or LDAP_SERVER_DOWN result code to your client. When you receive this result code, call the ldap_simple_bind_s() function. This function reestablishes a connection to one of the hosts specified in the ldap_init() or prldap_init() function call. If your client is able to reconnect with the server, ldap_simple_bind_s() issues a bind request to the server and returns the result. The following example attempts to reconnect to the server if the client is disconnected.

#include "ldap.h"
...
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 );