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
- Managing Memory
- Reporting Errors
- Calling Synchronous and Asynchronous Functions
- Handling Referrals
- Setting Up an In-Memory Cache
- Handling Failover
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
- Getting the Error Message
- Setting Error Codes
- Printing Out Error Messages
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:
- If you are calling asynchronous functions, you can get the
information from the
LDAPMessage
structure representing the result returned from the server. For details, see "Getting the Information from an LDAPMessage Structure." - In situations where you do not have 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. For details, see "Getting the Information from an LDAP Structure."
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 codeLDAP_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 codeLDAP_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 ); } ...
Printing Out Error Messages
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()
).
- For details on calling synchronous functions, see "Calling Synchronous Functions."
- For details on calling asynchronous functions, see "Calling Asynchronous Functions."
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:
- Checking if the LDAP Request was Sent
- Getting the Server Response
- Getting Information from the Server Response
- Canceling an Operation in Progress
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
- Enabling or Disabling Referral Handling
- Limiting Referral Hops
- Binding When Following Referrals
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
orLDAP_REFERRAL
). You can get the LDAP URLs from the result by calling theldap_parse_result()
function.
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 theldap_first_reference()
andldap_next_reference()
functions. You can also call theldap_first_message()
andldap_next_message()
functions to get each message in the search results, then call theldap_msgtype()
function to determine if the message is of the typeLDAP_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 toLDAP_OPT_OFF
. - If you want the client to automatically follow referrals again,
set the
optdata
parameter toLDAP_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
- Defining the Rebind Function
- Registering the Rebind Function
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:
- The LDAP server sends a referral back to the client. The referral contains an LDAP URL that identifies another LDAP server.
- The client calls the rebind function (the function specified by
the
LDAP_OPT_REBIND_FN
option), passing0
as thefreeit
argument. - The rebind function sets the
dnp
,passwdp
, andauthmethodp
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
).
- The
- 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.) - 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.
- The client calls the rebind function again, passing 1 as the
freeit
argument. - 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
).
arg
argument, which is a pointer to the argument specified in theldap_set_rebind_proc()
function. If successful, returnLDAP_SUCCESS
; otherwise, return the appropriate LDAP error code. - Set
- If
freeit
is1
, 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 |
---|---|
|
The connection handle to the LDAP server. |
|
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. |
|
A pointer to the user's (or entity's) password. Your function needs to set this value. |
|
A pointer to the method of authentication. Your function needs to set this value. |
|
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.
|
|
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 theLDAP_OPT_REBIND_FN
option to your function and theLDAP_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:
- Call the
ldap_memcache_init()
function to create a newLDAPMemCache
structure, which is the cache. Pass the pointer to this structure to subsequent operations. - 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 );