Sun Java System LDAP SDK for C Programming Guide |
Chapter 3
Writing an LDAP ClientWith the Sun Java System LDAP SDK for C, you can write a new application, or enable an existing application, to interact with an Lightweight Directory Access Protocol (LDAP) server. This chapter covers the procedures for connecting to an LDAP server, authenticating, requesting operations, and disconnecting from the server. It includes the following sections:
Designing an LDAP ClientThe following procedure outlines a typical process for communicating with an LDAP server.
- Initialize an LDAP session.
See "Initializing an LDAP Session" for details.
- Bind to the LDAP server, if necessary.
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.
- Close the connection to the LDAP server when finished.
See "Closing the Connection to the Server" for details.
Code Example 3-1 is source code for a client that requests a 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 distinguished name (DN) of any matching entry.
Initializing an LDAP SessionBefore connecting to an LDAP server, you need to initialize a session. As part of this process, you create an LDAP structure that contains information about the LDAP server and session. You then pass this LDAP structure (usually as a pointer) to all subsequent functions in order to identify the LDAP server with which you are working. Sample code for initializing an LDAP session is provided in the section, "Session Initialization Sample Code".
Note
If you plan to connect to the LDAP server over a Secure Sockets Layer (SSL), the procedure for initializing an LDAP session is different. For details, see Chapter 11, "Connecting Over SSL."
Specifying a Single LDAP Server
To initialize a single LDAP session, call ldap_init() and pass the host name and port number of your LDAP server as arguments to the function. If the server is using the default port 389 for the LDAP, you can pass LDAP_PORT as the value for the defport parameter as illustrated in Code Example 3-2.
Code Example 3-2 Passing an LDAP Server with the Default LDAP Port
...
LDAP *ld
...
ld = ldap_init( "directory.iplanet.com", LDAP_PORT );
If successful, ldap_init() returns a connection handle (a pointer to the LDAP structure that contains information about the connection) to the LDAP server. You need to pass this pointer to the API 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 to provide a context for the connection.
Caution
ldap_init() does not open a connection to the LDAP server. The actual connection opening will occur when the first operation is attempted.
Specifying a List of LDAP Servers
When initializing the LDAP session, you can also specify a list of LDAP servers for which you want to attempt connections. 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. In Code Example 3-3, 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.example2.com, port 389. If that server does not respond, the client will use the server on ld3.example.com, port 389.
Code Example 3-3 Passing Multiple LDAP Servers with Default LDAP Port
...
LDAP *ld
...
ld = ldap_init( "ld1.example.com ld2.example2.com
ld3.example.com", LDAP_PORT );
If any of the servers do not use the default LDAP port, use the host:port format to specify the server name and port number as the argument to ldap_init(). In Code Example 3-4, the 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.
Code Example 3-4 Passing Non-Default LDAP Ports
...
LDAP *ld
...
ld = ldap_init( "ld1.example.com ld2.example.com:38900",
LDAP_PORT );
Session Initialization Sample Code
Code Example 3-5 initializes a session with an LDAP server, specifying a list of LDAP servers to try: ldap.example.com:389 and directory.example.com:3890. It also sets a session preference that identifies the client as an LDAPv3 client. (After you initialize a session with an LDAP server, you can set session preferences. More information can be found in the section, "Setting Session Preferences".)
Note
Note that ldap_init() does not connect to the LDAP server right away.
Code Example 3-5 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 argument to identify the LDAP server. */
...
Setting Session PreferencesWith the LDAP SDK for C, you can set preferences for your client that you want applied to all LDAP sessions. To get or set the value of a preference, call either the ldap_get_option() or ldap_set_option() functions, respectively. Both functions pass two parameters (in addition to the ld parameter which represents the connection to the server):
- The option parameter identifies the option that you want to get or set.
- The value parameter 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.
Tip
You can set a preference for all connections created by passing NULL (rather than an LDAP structure which specifies the connection) as the first argument.
The following sections illustrate how to set two specific preferences. For a complete listing of the options and values you can get and set, see the documentation for the two functions in Chapter 17, "Function Reference."
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 communication with the server, you can set the LDAP_OPT_RECONNECT preference for the session. Once set, if your connection is lost, the client will attempt another bind (using the same authentication) to reestablish the connection.
To set the restart preference, 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 to LDAP_OPT_ON to specify 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 to LDAP_OPT_OFF to specify that you want to create a new connection handle to connect to the server. By default, the optdata parameter is set to LDAP_OPT_OFF. Both LDAP_OPT_OFF and LDAP_OPT_ON are cast to (void *). Code Example 3-6 illustrates how you can pass these parameters directly to the function.
Code Example 3-6 Passing Restart Preferences to Function
...
ldap_set_option( ld, LDAP_OPT_RECONNECT, LDAP_OPT_ON );
...
Specifying the LDAP Version of Your Client
If you plan to call functions that make use of LDAPv3 features (such as controls or extended operations), you should set the protocol version of your client to LDAPv3. By default, clients built with the Sun Java System LDAP SDK for C 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. Code Example 3-7 illustrates this.
Code Example 3-7 Passing the LDAP Protocol Version Number
...
version = LDAP_VERSION3;
ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
...
After setting this option, as part of the authentication process, your client sends the supported LDAP version number to the server allowing it to determine whether or not to enable LDAPv3 features.
Tip
The LDAPv3 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 it issues a bind.
Binding and Authenticating to an LDAP ServerWhen connecting to the LDAP server, your client may need to send a bind request. A bind request should be sent if:
The bind request should contain the following information:
Methods of Authentication
When binding to an LDAP server, you can use a number of different methods to authenticate your client. They include:
Simple Authentication
With simple authentication, your client provides the DN of the user and the user’s password to the LDAP server. For more information, see "Using Simple Authentication".
Note
You can also use this method to bind anonymously by providing NULL values as the user’s DN and password as described in "Binding Anonymously".
Certificate-based Client Authentication
With certificate-based client authentication, your client sends its certificate to the LDAP server. For more information, see Chapter 11, "Connecting Over SSL."
Simple Authentication and Security Layer (SASL)
SASL is detailed in the Simple Authentication and Security Layer (SASL) - RFC 2222 (http://www.faqs.org/rfcs/rfc2222.html). Some LDAPv3 servers (including Sun Java System Directory Server) support authentication through SASL. For more information, see Chapter 12, "Using SASL Authentication."
Note
If you are binding to Sun Java System 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" in Chapter 13, "Working with LDAP Controls."
Using Simple Authentication
If you plan to use simple authentication, call one of the following functions:
- ldap_simple_bind_s() is a synchronous function for use if you want to wait for the bind operation to complete before the function returns. See "Performing a Synchronous Authentication Operation" for more information.
- ldap_simple_bind() is an asynchronous function for use if you do not want to wait for the bind operation to complete. With this function, you can perform other work while periodically checking for the results of the bind operation. See "Performing an Asynchronous Authentication Operation" for more information.
Note
For more information on the difference between synchronous and asynchronous functions, see "Synchronous and Asynchronous Functions" in Chapter 4, "Using the LDAP API."
Performing a Synchronous Authentication Operation
If you want to wait for the bind operation to complete before continuing, call ldap_simple_bind_s(). This function returns LDAP_SUCCESS if the operation completed successfully or an LDAP result code if a problem occurred. (See ldap_simple_bind_s() in Chapter 17, "Function Reference" for a list of result codes returned.)
Note
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 incorrect, you need to write code to perform the check before you call ldap_simple_bind_s().
Code Example 3-8 uses the synchronous ldap_simple_bind_s() function to authenticate user Barbara Jensen to the LDAP server.
Code Example 3-8 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 ldap_simple_bind(). This function sends a LDAP bind request to the server and returns a message ID identifying the bind operation. 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 this message ID. If your client has received the results, ldap_result() passes back the information in an LDAPMessage structure. To retrieve error information from LDAPMessage, you can pass it to the ldap_parse_result() function. ldap_parse_result() 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.
Note
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 incorrect, you need to write code to perform the check before you call ldap_simple_bind().
Code Example 3-9 uses the asynchronous ldap_simple_bind() function to authenticate user Barbara Jensen to the LDAP server.
Code Example 3-9 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 users don’t need special access permissions for the operation, you don’t need to authenticate before performing the search. 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 as illustrated in Code Example 3-10.
With LDAPv2, the client is required to send a bind request, even when binding anonymously (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.
Performing LDAP OperationsOnce 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). Table 3-1 lists some of these LDAP operations and where you can get more information about them.
Table 3-1 Performing LDAP Operations
To Perform This Operation
Call This API Function
For More Information
Search for entries
Determine an attribute’s value
Add entries
Modify entries
Delete entries
Change DN of entries
Most LDAP operations can be performed synchronously or asynchronously. The functions with names ending in _s are synchronous, and the remaining ones are asynchronous. For more information on the distinction between calling synchronous and asynchronous functions, see "Synchronous and Asynchronous Operations" of Chapter 1, "Introduction" as well as "Synchronous and Asynchronous Functions" of Chapter 4, "Using the LDAP API.")
Closing the Connection to the ServerWhen you have finished performing all necessary LDAP operations, you need to close the connection to the LDAP server. After you close the connection, you can no longer use the LDAP structure as it is freed from memory. To close a connection to an LDAP server, call one of the following functions:
Both ldap_unbind() and ldap_unbind_s() are identical synchronous functions. They use different names so that each has a corresponding authentication function (ldap_simple_bind() and ldap_simple_bind_s()) to close the server connection. The ldap_unbind_ext() function allows you to explicitly include both server and client controls in your unbind request. However, 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. Code Example 3-11 closes the current connection with the LDAP server.
Code Example 3-11 Closing an LDAP Server Connection
#include <stdio.h>
#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 ) );
}
...