Mozilla LDAP C SDK Programmer's Guide
Chapter 4 - Writing an LDAP Client
This chapter describes the general process of writing an LDAP client. The chapter covers the procedures for connecting to an LDAP server, authenticating, requesting operations, and disconnecting from the server.
The chapter includes the following sections:
- Overview: Designing an LDAP Client
- Initializing an LDAP Session
- Binding and Authenticating to an LDAP Server
- Performing LDAP Operations
- Closing the Connection to the Server
The next chapter, Chapter 5 - Using the LDAP API also includes important information on API functions.
Overview: Designing an LDAP Client
With the Mozilla LDAP C SDK, you can write a new application or enable an existing application to interact with an LDAP server. The following procedure outlines the typical process of communicating with an LDAP server. Follow these steps when writing your LDAP client:
- Initialize an LDAP session. (See "Initializing an LDAP Session" for details.) Set any preferences that you want applied to all LDAP sessions for your client (see "Setting Preferences" for details). If you intend to use any of the LDAPv3 features (such as controls or operations), specify the LDAP version of your client, as detailed in "Specifying the LDAP Version of Your Client" (the default LDAP version used by the Mozilla LDAP C SDK is version 2).
- If necessary, bind to the LDAP server. (See "Binding and Authenticating to an LDAP Server" for details.)
- Perform LDAP operations, such as searching the directory or modifying entries in the directory. (See "Performing LDAP Operations" for details.)
- When you are done, close the connection to the LDAP server. (See "Closing the Connection to the Server" for details.)
The following is a simple example of an LDAP client that requests
an LDAP search operation from a server. The client connects to the
LDAP server running on the local machine at port 389, searches the
directory for entries with the last name "Jensen"
("sn=Jensen
"), and prints out the DNs of any matching
entries.
Code Example 4-1 - An LDAP search operation
#include <stdio.h> #include "ldap.h" /* Specify the search criteria here. */ #define HOSTNAME "localhost" #define PORTNUMBER 389 #define BASEDN "dc=example,dc=com" #define SCOPE LDAP_SCOPE_SUBTREE #define FILTER "(sn=Jensen)" int main( int argc, char **argv ) { LDAP *ld; LDAPMessage *result, *e; char *dn; int version, rc; /* Print out an informational message. */ printf( "Connecting to host %s at port %d...\n\n", HOSTNAME, PORTNUMBER ); /* STEP 1: Get a handle to an LDAP connection and set any session preferences. */ if ( (ld = ldap_init( HOSTNAME, PORTNUMBER )) == NULL ) { perror( "ldap_init" ); return( 1 ); } /* Use the LDAP_OPT_PROTOCOL_VERSION session preference to specify that the client is an LDAPv3 client. */ version = LDAP_VERSION3; ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version ); /* STEP 2: Bind to the server. In this example, the client binds anonymously to the server (no DN or credentials are specified). */ rc = ldap_simple_bind_s( ld, NULL, NULL ); if ( rc != LDAP_SUCCESS ) { fprintf(stderr, "ldap_simple_bind_s: %s\n", ldap_err2string(rc)); return( 1 ); } /* Print out an informational message. */ printf( "Searching the directory for entries\n" " starting from the base DN %s\n" " within the scope %d\n" " matching the search filter %s...\n\n", BASEDN, SCOPE, FILTER ); /* STEP 3: Perform the LDAP operations. In this example, a simple search operation is performed. The client iterates through each of the entries returned and prints out the DN of each entry. */ rc = ldap_search_ext_s( ld, BASEDN, SCOPE, FILTER, NULL, 0, NULL, NULL, NULL, 0, &result ); if ( rc != LDAP_SUCCESS ) { fprintf(stderr, "ldap_search_ext_s: %s\n", ldap_err2string(rc)); return( 1 ); } for ( e = ldap_first_entry( ld, result ); e != NULL; e = ldap_next_entry( ld, e ) ) { if ( (dn = ldap_get_dn( ld, e )) != NULL ) { printf( "dn: %s\n", dn ); ldap_memfree( dn ); } } ldap_msgfree( result ); /* STEP 4: Disconnect from the server. */ ldap_unbind( ld ); return( 0 ); } ...
The rest of this chapter explains how to initialize a session, authenticate your client, perform LDAP operations, and disconnect from the LDAP server.
Initializing an LDAP Session
Before you can connect to an LDAP server, you need to initialize
an LDAP session. As part of this process, you create an
LDAP
structure, which
contains information about the LDAP server and the LDAP session. You
need to pass this LDAP
structure (usually as a pointer) to all subsequent LDAP API
functions to identify the LDAP server that you are working with.
The following sections explain how to initialize an LDAP session with a server and how to specify session preferences:
An example of initializing an LDAP session is also provided. See "Examples of Initializing an LDAP Session."
Note:
If you plan to connect to the LDAP server over a Secure Sockets Layer (SSL), the procedure for initializing an LDAP session differs. For details, see Chapter 12 - Connecting Over SSL.
Specifying a Single LDAP Server
To initialize the LDAP session, call the
ldap_init()
function.
Pass the host name and port number of your LDAP server as arguments
to this function.
If the server is using the default port for the LDAP protocol
(port 389), you can pass LDAP_PORT
as the value of the
port
parameter. For example:
... LDAP *ld ... ld = ldap_init( "directory.example.com", LDAP_PORT );
If successful, the
ldap_init()
function
returns a connection handle, which is a pointer to an
LDAP
structure that
contains information about the connection to the LDAP server. You
need to pass this pointer to the LDAP API functions for connecting,
authenticating, and performing LDAP operations on a server.
For example, when you call a function to search the directory, you need to pass the connection handle as a parameter. The connection handle provides context for the connection.
Specifying a List of LDAP Servers
When initializing the LDAP session, you can specify a list of LDAP servers that you want to attempt to connect to. If the first LDAP server in the list does not respond, the client will attempt to connect to the next server in the list.
To specify a list of LDAP servers, pass a space-delimited list of
the host names as the first argument to the
ldap_init()
function.
For example:
... LDAP *ld ... ld = ldap_init( "ld1.example.com ld2.example.com ld3.example.com", LDAP_PORT );
In the example above, the LDAP client will attempt to connect to
the LDAP server on ld1.example.com
, port 389. If that
server does not respond, the client will attempt to connect to the
LDAP server on ld2.iplaent.com
, port 389. If that
server does not respond, the client will use the server on
ld3.example.com
, port 389.
If any of the servers do not use the default port specified as
the second argument to
ldap_init()
, use the
host:
port format to
specify the server name. For example:
... LDAP *ld ... ld = ldap_init( "ld1.example.com ld2.example.com:38900", LDAP_PORT );
In the example above, the LDAP client will attempt to connect to
the LDAP server on ld1.
example.com
, port
389. If that server does not respond, the client will attempt to
connect to the LDAP server on ld2.example.com
, port
38900.
Setting Preferences
To get or set the value of a preference, call the
ldap_get_option()
function or the
ldap_set_option()
function.
Both functions pass two parameters (in addition to the
ld
parameter, which represents the connection to the
server):
-
An
option
parameter that identifies the option that you want to get or set. -
A
value
parameter that is either a pointer to a place to put the value that you are getting or a pointer to the value that you are setting.
Note that you can set a preference for all connections created by
passing NULL
instead of an
LDAP structure as the first argument.
For a complete list of the options and values you can get and
set, see the documentation on the
ldap_set_option()
function.
Some of these preferences are described in the sections below:
Setting the Restart Preference
If communication with the LDAP server is interrupted, the result
code LDAP_SERVER_DOWN
is returned by your client. If
you want your client to continue to attempt to communicate with the
server, you can set the LDAP_OPT_RECONNECT
preference
for the session. If your connection is lost and this option is set,
the application will attempt another bind using the same
authentication to reestablish the connection.
Call the ldap_set_option()
function and pass
LDAP_OPT_RECONNECT
as the value of the
option
parameter.
-
If you want the client to resume LDAP I/O operations automatically,
set the
optdata
parameter toLDAP_OPT_ON
(this specifies that the same connection handle can be used to reconnect to the server). -
If you do not want the client to resume I/O operations, set the
optdata
parameter toLDAP_OPT_OFF
(specifies that you want to create a new connection handle to connect to the server).
By default, this option is set to LDAP_OPT_OFF
. Both
LDAP_OPT_OFF
and LDAP_OPT_ON
are cast to
(void *)
. You can pass these parameters directly to the
function. For example:
... ldap_set_option( ld, LDAP_OPT_RECONNECT, LDAP_OPT_ON ); ...
Specifying the LDAP Version of Your Client
If you plan to call API functions that make use of LDAPv3 features, you should set the protocol version of your client to LDAPv3. (By default, clients built with the Mozilla LDAP C SDK identify themselves to LDAP servers as LDAPv2 clients.)
To specify the LDAP version supported by your client, call the
ldap_set_option()
function and set the LDAP_OPT_PROTOCOL_VERSION
option
to the value 3
. For example:
... version = LDAP_VERSION3; ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version ); ...
After setting this option, your client can authenticate or bind to the server (see "Binding and Authenticating to an LDAP Server" for details). As part of the process of binding to the server, your client sends the supported LDAP version number to the server. This allows the server to determine whether or not to enable use of LDAPv3 features.
Note that the LDAPv3 protocol allows you to perform LDAP operations without first binding to the server. An LDAPv3 server will assume that the client is LDAPv3 compliant if the client issues non-bind operations before is issues a bind.
Examples of Initializing an LDAP Session
The following section of code initializes a session with an LDAP
server. The example specifies a list of LDAP servers to try:
ldap.
example.com:389
and
directory.
example.com:3890
. The example
also sets a session preference that identifies the client as an
LDAPv3 client.
Code Example 4-2 - Initializing an LDAP session
#include <stdio.h> #include <ldap.h> ... LDAP *ld; int ldap_default_port, version; /* Specify list of LDAP servers that you want to try connecting to. */ char *ldap_host = "ldap.example.com directory.example.com:3890"; /* If the LDAP server is running on the standard LDAP port (port 389), * you can use LDAP_PORT to identify the port number. */ ldap_default_port = LDAP_PORT; ... /* Initialize the session with the LDAP servers. */ if ( ( ld = ldap_init( ldap_host, ldap_default_port ) ) == NULL ) { perror( "ldap_init" ); return( 1 ); } /* Specify the LDAP version supported by the client. */ version = LDAP_VERSION3; ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version ); ... /* Subsequent API calls pass ld as an argument to identify the LDAP server. */ ...
After you initialize a session with an LDAP server, you can set
session options and connect and authenticate to the LDAP server.
(Note that the
ldap_init()
function
does not connect to the LDAP server right away.)
Binding and Authenticating to an LDAP Server
When connecting to the LDAP server, your client may need to send a bind operation request to the server. This is also called binding to the server.
An LDAP bind request contains the following information:
- The LDAP version of the client.
- The DN that the client is attempting to authenticate as.
- The method of authentication that should be used.
- The credentials to be used for authentication.
Your client should send a bind request to the server in the following situations:
- You want to authenticate to the server. For example, you may want to add or modify entries in the directory, which requires you to authenticate as a user with certain access privileges.
- You are connecting to an LDAPv2 server. LDAPv2 servers typically require clients to bind before any operations can be performed.
LDAP clients can also bind as an anonymous clients to the server (for example, the LDAP server may not require authentication if your client is just searching the directory).
This chapter explains how to set up your client to bind to an LDAP server. Topics covered here include:
Understanding Authentication Methods
When binding to an LDAP server, you can use a number of different methods to authenticate your client:
-
Simple authentication. With simple
authentication, your clients provides the distinguished name of
the user and the user's password to the LDAP server.
You can also use this method to bind as an anonymous client by
providing
NULL
values as the user's distinguished name and password (as described in "Binding Anonymously"). - Certificate-based client authentication (over SSL). With certificate-based client authentication, your client sends its certificate to the LDAP server. The certificate identifies your LDAP client. For more information on using certificate-based client authentication, see Chapter 12 - Connecting Over SSL.
- Simple Authentication and Security Layer (SASL). SASL is described in Simple Authentication and Security Layer (SASL), RFC 2222. For more information, refer to Where to Find Additional Information. Some LDAPv3 servers (including the Netscape Directory Server 6.0 and later) support authentication through SASL. For more information on using SASL mechanisms for authentication, see Chapter 13 - Using SASL Authentication.
Using Simple Authentication
If you plan to use simple authentication, call one of the following functions:
-
Call the synchronous
ldap_simple_bind_s()
function if you want to wait for the bind operation to complete before the function returns. (See "Performing a Synchronous Authentication Operation.") -
Call the asynchronous
ldap_simple_bind()
function if you do not want to wait for the bind operation to complete. You can perform other work while periodically checking for the results of the bind operation. (See "Performing an Asynchronous Authentication Operation.")
Note that if you specify a DN but no password, your client will
bind to the server anonymously. If you want a NULL
password to be rejected as an incorrect password, you need to write
code to perform the check before you call the
ldap_simple_bind()
or
the ldap_simple_bind_s()
functions.
If you are binding to the Netscape Directory Server, the server may send back special controls to indicate that your password has expired or will expire in the near future. For more information on these controls, see "Using Password Policy Controls."
For more information about the difference between synchronous and asynchronous functions, see "Calling Synchronous and Asynchronous Functions."
Performing a Synchronous Authentication Operation
If you want to wait for the bind operation to complete before
continuing, call the
ldap_simple_bind_s()
function. This function returns LDAP_SUCCESS
if the
operation successfully completed or an LDAP result code if a problem
occurred. (See the documentation for this function for a list of
possible result codes returned.)
If you specify a DN but no password, your client will bind to the
server anonymously. If you want a NULL
password to be
rejected as an incorrect password, you need to write code to perform
the check before you call the
ldap_simple_bind_s()
function.
The following section of code uses the synchronous
ldap_simple_bind_s()
function to authenticate the user
Barbara Jensen to the LDAP server. (For an example of binding anonymously,
see "Binding Anonymously.")
Code Example 4-3 - Synchronous authentication
#include <stdio.h> #include "ldap.h" /* Change these as needed. */ #define HOSTNAME "localhost" #define PORTNUMBER LDAP_PORT #define BIND_DN "uid=bjensen,ou=People,dc=example,dc=com" #define BIND_PW "hifalutin" LDAP *ld; int rc; /* Get a handle to an LDAP connection. */ if ( (ld = ldap_init( HOSTNAME, PORTNUMBER )) == NULL ) { perror( "ldap_init" ); return( 1 ); } /* Print out an informational message. */ printf( "Binding to server %s:%d\n", HOSTNAME, PORTNUMBER ); printf( "as the DN %s ...\n", BIND_DN ); /* Bind to the LDAP server. */ rc = ldap_simple_bind_s( ld, BIND_DN, BIND_PW ); if ( rc != LDAP_SUCCESS ) { fprintf(stderr, "ldap_simple_bind_s: %s\n\n", ldap_err2string(rc)); return( 1 ); } else { printf( "Bind operation successful.\n" ); } ... /* If you want, you can perform LDAP operations here. */ ... /* Disconnect from the server when done. */ ldap_unbind( ld ); return( 0 ); ...Performing an Asynchronous Authentication Operation
If you want to perform other work (in parallel) while waiting for
the bind operation to complete, call the
ldap_simple_bind()
function. This function sends an LDAP bind request to the server and
returns a message ID identifying the bind operation.
If you specify a DN but no password, your client will bind to the
server anonymously. If you want a NULL
password to be
rejected as an incorrect password, you need to write code to perform
the check before you call the
ldap_simple_bind()
function.
You can check to see if your client has received the results of
the bind operation from the server by calling the
ldap_result()
function
and passing it the message ID.
If your client has received the results, the
ldap_result()
function passes back the results of the
bind operation in an
LDAPMessage
structure.
To get error information from this structure, you can pass it to the
ldap_parse_result()
function.
The ldap_parse_result()
function gets the LDAP
result code of the operation and any error messages sent back from
the server. This function also retrieves any controls sent back from
the server.
(Netscape Directory Server may return a control if the user's password has expired or will expire in the near future. For more information, see "Using Password Policy Controls.")
The following section of code uses the asynchronous
ldap_simple_bind()
function to authenticate the user
Barbara Jensen to the LDAP server. (For an example of binding
anonymously, see "Binding
Anonymously.")
Code Example 4-4 - Asynchronous authentication
#include <stdio.h> #include "ldap.h" void do_other_work(); int global_counter = 0; ... #define HOSTNAME "localhost" #define PORTNUMBER LDAP_PORT #define BIND_DN "uid=bjensen,ou=People,dc=example,dc=com" #define BIND_PW "hifalutin" ... 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; /* Specify the timeout period for ldap_result(), which specifies how long the function should block when waiting for results from the server. */ zerotime.tv_sec = zerotime.tv_usec = 0L; /* Get a handle to an LDAP connection. */ if ( (ld = ldap_init( HOSTNAME, PORTNUMBER )) == NULL ) { perror( "ldap_init" ); return( 1 ); } /* Print out an informational message. */ printf( "Binding to server %s:%d\n", HOSTNAME, PORTNUMBER ); printf( "as the DN %s ...\n", BIND_DN ); /* Send an LDAP bind request to the server. */ msgid = ldap_simple_bind( ld, BIND_DN, BIND_PW ); /* If the returned message ID is less than zero, an error occurred. */ if ( msgid < 0 ) { rc = ldap_get_lderrno( ld, NULL, NULL ); fprintf(stderr, "ldap_simple_bind : %s\n", ldap_err2string(rc)); ldap_unbind( ld ); return( 1 ); } /* Check to see if the bind operation completed. */ while ( !finished ) { rc = ldap_result( ld, msgid, 0, &zerotime, &res ); switch ( rc ) { /* If ldap_result() returns -1, error occurred. */ case -1: rc = ldap_get_lderrno( ld, NULL, NULL ); fprintf( stderr, "ldap_result: %s\n", ldap_err2string( rc ) ); ldap_unbind( ld ); return ( 1 ); /* If ldap_result() returns 0, the timeout (specified by the timeout argument) has been exceeded before the client received the results from the server. Continue calling ldap_result() to poll for results from the server. */ case 0: break; default: /* The client has received the result of the bind operation. */ finished = 1; /* Parse this result to determine if the operation was successful. Note that a non-zero value is passed as the last parameter, which indicates that the LDAPMessage structure res should be freed when done. (No need to call ldap_msgfree().) */ 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 operation. */ if ( rc != LDAP_SUCCESS ) { fprintf( stderr, "ldap_simple_bind: %s\n", ldap_err2string( rc ) ); /* If the server sent an additional error message, print it out. */ if ( error_msg != NULL && *error_msg != '\0' ) { fprintf( stderr, "%s\n", error_msg ); } /* If 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.) */ if ( matched_msg != NULL && *matched_msg != '\0' ) { fprintf( stderr, "Part of the DN that matches an existing entry: %s\n", matched_msg ); } ldap_unbind( ld ); return( 1 ); } else { printf( "Bind operation successful.\n" ); printf( "Counted to %d while waiting for bind op.\n", global_counter ); } break; } /* Do other work here while waiting for results from the server. */ if ( !finished ) { do_other_work(); } } ... /* If you want, you can perform LDAP operations here. */ ... /* Disconnect from the server when done. */ ldap_unbind( ld ); return( 0 ); ... /* Function that does work while waiting for results from the server. */ void do_other_work() { global_counter++; } ...
Binding Anonymously
In some cases, you may not need to authenticate to the LDAP server. For example, if you are writing a client to search the directory (and if users don't need special access permissions to search), you might not need to authenticate before performing the search operation.
With LDAPv2, the client is required to send a bind request, even when binding anonymously (binding without specifying a name or password). With LDAPv3, the client is no longer required to bind to the server if the client does not need to authenticate.
To bind as an anonymous user, call
ldap_simple_bind()
or
ldap_simple_bind_s()
,
and pass NULL
values for the who
and
passwd
parameters. For example:
... rc = ldap_simple_bind_s( ld, NULL, NULL ); ...
Performing LDAP Operations
Once you initialize a session with an LDAP server and complete the authentication process, you can perform LDAP operations, such as searching the directory, adding new entries, updating existing entries, and removing entries (provided the server's access control allows these operations).
To perform LDAP operations, call these API functions:
-
To search for entries in the directory, call
ldap_search_ext()
orldap_search_ext_s()
. (See Chapter 6 - Searching the Directory for details.) -
To determine whether or not an attribute contains a specified
value, call
ldap_compare_ext()
orldap_compare_ext_s()
. (See "Comparing the Value of an Attribute" for details.) -
To add entries to the directory, call
ldap_add_ext()
orldap_add_ext_s()
. (See "Adding a New Entry" for details.) -
To modify entries in the directory, call
ldap_modify_ext()
orldap_modify_ext_s()
. (See "Modifying an Entry" for details.) -
To delete entries from the directory, call
ldap_delete_ext()
orldap_delete_ext_s()
. (See "Deleting an Entry" for details.) -
To change the DNs of entries in the directory, call
ldap_rename()
orldap_rename_s()
. (See "Changing the DN of an Entry" for details.)
Most LDAP operations can be performed synchronously or
asynchronously. The functions with names ending in _s
are synchronous functions, and the remaining functions are
asynchronous functions. (For more information on the distinction
between calling synchronous and asynchronous functions, see
"Calling Synchronous and Asynchronous
Functions.")
Closing the Connection to the Server
When you have finished performing all necessary LDAP operations, you need to close the connection to the LDAP server.
To close a connection to an LDAP server, call one of the following functions:
Both ldap_unbind()
and ldap_unbind_s()
are synchronous functions. These functions are identical; they use
different names so that each authentication function
(ldap_simple_bind()
and ldap_simple_bind_s()
)
has a corresponding function for closing the server connection.
After you close the connection, you can no longer use the
LDAP structure. Calling any of the
unbind functions frees the LDAP
structure from memory.
The following code closes the current connection with the LDAP server:
Code Example 4-5 - Closing an LDAP server connection
#include "ldap.h" ... LDAP *ld; int rc; ... /* After completing your LDAP operations with the server, close the connection. */ rc = ldap_unbind( ld ); if ( rc != LDAP_SUCCESS ) { fprintf( stderr, "ldap_unbind: %s\n", ldap_err2string( rc ) ); } ...
The ldap_unbind_ext()
function allows you to
explicitly include both server and client controls in your unbind
request. However, note that since there is no server response to an
unbind request, there is no way to receive a response from a server
control that is included with your unbind request.