Mozilla LDAP SDK Programmer's Guide/Writing an LDAP Client With LDAP C SDK

From MozillaWiki
Jump to: navigation, search

With LDAP C SDK, you can write a new application. You can also enable an existing application to interact with a Lightweight Directory Access Protocol (LDAP) server. This section explains how to connect to an LDAP server, authenticate, request operations, and disconnect from the server.

Designing an LDAP Client With LDAP C SDK

The following procedure outlines a typical process for communicating with an LDAP server.

To Communicate With an LDAP Server

  1. Initialize an LDAP session.
    See Initializing an LDAP Session With LDAP C SDK for details.
  2. Bind to the LDAP server, if necessary.
    See Binding and Authenticating to an LDAP Server With LDAP C SDK for details.
  3. Perform LDAP operations, such as searching the directory or modifying entries in the directory.
    See Performing LDAP Operations With LDAP C SDK for details.
  4. Close the connection to the LDAP server when finished.
    See Closing the Connection to an LDAP Server With LDAP C SDK for details.

This sample source code shows a client that requests an LDAP search operation from a server. The LDAP server runs on the local system on port 389. The client searches the directory for entries with the last name Jensen (sn=Jensen), and prints the distinguished name (DN) of any matching entry.

#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. Use prldap_init() for IPv6. */
  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 );
}
...

Initializing an LDAP Session With LDAP C SDK

Before connecting to an LDAP server, you must 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 Example Session Initialization.

Note: If you plan to connect to the LDAP server over the Secure Sockets Layer (SSL) protocol, the procedure for initializing an LDAP session is different. For details, see SSL Connections With LDAP C SDK.


Specifying a Single LDAP Server


To initialize an LDAP session, call ldap_init(), or prldap_init() for IPv6 support, with the host name and port number of the LDAP server. If the server is using the default port 389 for the LDAP server, pass LDAP_PORT as the value for the defport parameter as shown here.

...
LDAP *ld
...
ld = ldap_init( "directory.example.com", LDAP_PORT );

If successful, ldap_init(), or prldap_init(), returns a connection handle to the LDAP server. A connection handle is a pointer to the LDAP structure that contains information about the connection. You must pass this pointer to the API for connecting, authenticating, and performing LDAP operations on a server. For example, when you search the directory, you pass the connection handle as a parameter to provide a context for the connection.

Note: The initialization function does not open a connection to the LDAP server. The actual opening of a connection occurs 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 attempts 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() or prldap_init() function. In the following example, the LDAP client attempts to connect to the LDAP server on ld1.example.com, port 389. If that server does not respond, the client attempts to connect to the LDAP server on ld2.example2.com, port 389. If that server does not respond, the client uses the server on ld3.example.com, port 389.

...
LDAP *ld
...
ld = ldap_init( "ld1.example.com ld2.example2.com
  ld3.example.com", LDAP_PORT );

If any servers do not use the default LDAP port, use the host:port format to specify the server name and port number. In the following example, that means ld1.example.com, port 389. If that server does not respond, the client attempts to connect to the LDAP server on ld2.example.com, port 1389.

...
LDAP *ld
...
ld = ldap_init( "ld1.example.com ld2.example.com:1389",
   LDAP_PORT );

Example Session Initialization


The following example initializes a session with an LDAP server, specifying a list of LDAP servers to try: ldap.example.com:389 and directory.example.com:1389. The example also sets a session preference that identifies the client as an LDAP v3 client. This session initialization code uses the prldap_init function, which works on IPv6 networks. Notice that prldap_init does not connect to the LDAP server right away.

After you initialize a session with an LDAP server, you can set session preferences. For information, see Setting Session Preferences With LDAP C SDK.

#include <stdio.h>
#include "ldappr.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:1389";

/* 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 = prldap_init( ldap_host, ldap_default_port, NULL ) ) == NULL ) {
  perror( "prldap_init" );
  return( 1 );
}

/* Specify the LDAP version supported by the client. */
version = LDAP_VERSION3;
ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );

...
/* Subsequent calls pass ld as argument to identify the LDAP server. */

...

Setting Session Preferences With LDAP C SDK


With LDAP C SDK, 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 the ldap_get_option() or ldap_set_option() functions respectively.

  • 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 to get, or a pointer to the value to set.

You can set a preference for all connections by passing NULL as the first argument, not an LDAP structure that specifies the connection.


Reconnecting Automatically


If communication with the LDAP server is interrupted, the server returns LDAP_SERVER_DOWN. 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 attempts another bind with the same authentication to reestablish the connection.

The following example shows that to set the reconnect preference, call the ldap_set_option() function and pass LDAP_OPT_RECONNECT as the value of the option parameter. To resume LDAP I/O operations automatically, set the optdata parameter to LDAP_OPT_ON. This setting specifies the same connection handle that can be used to reconnect to the server.

ldap_set_option( ld, LDAP_OPT_RECONNECT, LDAP_OPT_ON );

To avoid resuming I/O operations, you would set the optdata parameter to LDAP_OPT_OFF. This setting specifies 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 *).


Specifying the LDAP Version of Your Client


If you plan to call functions that use LDAP v3 features such as controls or extended operations, set the protocol version to LDAP v3. By default, clients built with LDAP C SDK identify themselves to LDAP servers as LDAP v3 clients, but that was not the case with previous versions.

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.

...
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. By setting the version, you allow the server to determine whether or not to enable LDAP v3 features.

LDAP v3 allows you to perform LDAP operations without first binding to the server. An LDAP v3 server assumes that the client is LDAP v3 compliant if the client issues non-bind operations before the client issues a bind.


Setting Connection Timeout


Clients that use LDAP C SDK can control the TCP/IP level timeout. When the TCP/IP timeout option is not set, an attempt to connect to a server blocks until the connection completes or the system times out. By using the LDAP_X_OPT_CONNECT_TIMEOUT option, you can adjust how long to wait for a connection.

You specify the timeout as an int number of milliseconds, then call the ldap_set_option() function, passing the LDAP_X_OPT_CONNECT_TIMEOUT option.

The following example sets the connection timeout to one second.

int timeout = 1000; /* 1000 milliseconds == 1 second */
ldap_set_option( ld, LDAP_X_OPT_CONNECT_TIMEOUT, timeout );
  • LDAP_X_IO_TIMEOUT_NO_WAIT
    Return immediately if the server cannot be reached.
  • LDAP_X_IO_TIMEOUT_NO_TIMEOUT
    Wait indefinitely for the server to connect.

By passing NULL as the first parameter to the ldap_set_option() function, you can set the default timeout for all connections used by your application.

Binding and Authenticating to an LDAP Server With LDAP C SDK

When connecting to the LDAP server, your client might need to send a bind request.

  • You want to authenticate to the server to add or modify entries in a directory that requires authentication as a user with certain access privileges.
  • You are connecting to an LDAP v2 server. LDAP v2 servers typically require clients to bind before any operations can be performed.
  • LDAP version of the client
  • Method of authentication to use
  • DN that the client is attempting to authenticate as
  • Credentials to be used for authentication

LDAP clients can also bind anonymously to the LDAP server if, for example, the server is configured not to require authentication for a simple directory search.


Using Simple Authentication With LDAP C SDK


  • 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.
  • 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.

For more information about the differences between the types of functions, see Synchronous and Asynchronous Functions.


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 ldap_simple_bind_s() for a list of result codes returned.

If you specify a DN but no password, your client binds to the server anonymously. If you want a NULL password to be rejected as incorrect, you must write code to perform the check before you call ldap_simple_bind_s().

The following example uses the synchronous ldap_simple_bind_s() function to authenticate user Barbara Jensen to the LDAP server.

<#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. Use prldap_init() for IPv6. */
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 an LDAP bind request to the server and returns a message ID identifying the bind operation. To see if your client has received the results of the bind operation, call the ldap_result() function with the 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 the message ID 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.

If you specify a DN but no password, your client binds 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().

The following example uses the asynchronous ldap_simple_bind function() to authenticate user Barbara Jensen to the LDAP server.

#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. Use prldap_init() for IPv6. */
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. */
      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 With LDAP C SDK


In some cases, you do not need to authenticate to the LDAP server. For example, if users are performing a search that has no special access permissions, you need not 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.

rc = ldap_simple_bind_s( ld, NULL, NULL );

With LDAP v2, the client is required to send a bind request, even when binding anonymously. That is, bind without specifying a name or password. With LDAP v3, the client is no longer required to bind to the server if the client does not need to authenticate.


Performing LDAP Operations With LDAP C SDK


After initializing a session with a server and completing the authentication process, you can perform LDAP operations. The LDAP operations include searching the directory, adding new entries, updating existing entries, and removing entries. The following lists LDAP operations and the functions that you can call to perform the operations.

Functions for Performing LDAP Operations
To Perform This Operation
Call This API Function
Search for entries
ldap_search_ext(), ldap_search_ext_s()
Determine an attribute value
ldap_compare_ext(), ldap_compare_ext_s()
Add entries
ldap_add_ext(), ldap_add_ext_s()
Modify entries
ldap_modify_ext(), ldap_modify_ext_s()
Delete entries
ldap_delete_ext(), ldap_delete_ext_s()
Change DN of entries
ldap_rename_ext(), ldap_rename_ext_s()

Most LDAP operations can be performed synchronously or asynchronously. The functions with names that end in _s are synchronous. The remaining ones are asynchronous. For more information about the distinction between the functions, see Synchronous and Asynchronous Operations.

Closing the Connection to an LDAP Server With LDAP C SDK

When 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 because the structure is freed from memory.

  • ldap_unbind()
  • ldap_unbind_s()
  • ldap_unbind_ext()

Both ldap_unbind() and ldap_unbind_s() are identical synchronous functions. These functions use different names so that each function 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 include explicitly both server and client controls in your unbind request. However, as the server does not respond to an unbind request, you cannot receive a response from a server control attached to your unbind request.

The following example closes the current connection with the LDAP server.

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