Plug-in Programmer’s Guide
Red Hat Directory Server                                                            

Previous
Contents
Index
Next

Chapter 8

Defining Functions for Authentication


This chapter explains how to write a plug-in function to bypass or replace the standard function for authentication with your own function.

Information on authentication with the Red Hat Directory Server (Directory Server) is organized in the following sections:

Understanding Authentication Methods

Authentication methods for LDAP is described in RFC 2829, which you can find at http://www.ietf.org/rfc/rfc2829.txt

Two methods that you can use to authenticate clients are simple authentication and SASL authentication:

Simple authentication provides minimal facilities for authentication. In the simple authentication method, clients send a DN and password to the server for authentication. The server compares the password sent by the client against the password stored in the client's directory entry.
SASL provides the means to use mechanisms other than simple authentication and SSL to authenticate to the Directory Server.

How the Directory Server Identifies Clients

The server keeps track of the identity of the LDAP client through the SLAPI_CONN_DN and SLAPI_CONN_AUTHTYPE parameters.

During an LDAP bind operation, the server authenticates the user and puts the DN and authenticated method in the SLAPI_CONN_DN and SLAPI_CONN_AUTHTYPE parameters.

When an authenticated client requests the server to perform an LDAP operation, the server checks the DN in the SLAPI_CONN_DN parameter to determine if the client has the appropriate access rights.

How the Authentication Process Works

When the Directory Server receives an LDAP bind request from a client, it processes the request in the following steps:

  1. The server parses the LDAP bind request and retrieves the following information:
    • The DN as which the client is attempting to authenticate.
    • The method of authentication used.
    • Any credentials (such as a password) included in the request.
If the method of authentication is LDAP_AUTH_SASL (SASL authentication), the server also retrieves the name of the SASL mechanism used from the LDAP bind request.
  1. The server normalizes the DN retrieved from the request. (See the slapi_dn_normalize() function for more information on normalized DNs.)
  2. The server retrieves any LDAPv3 controls included with the LDAP bind request.
  3. If the method of authentication is LDAP_AUTH_SASL (SASL authentication), the server determines whether the SASL mechanism (specified in the request) is supported.
If the SASL mechanism is not supported by the server, the server sends an LDAP_AUTH_METHOD_NOT_SUPPORTED result code back to the client and ends the processing of the bind request.
  1. If the method of authentication is LDAP_AUTH_SIMPLE (simple authentication), the server checks if the DN is an empty string or if there are no credentials.
If the DN is an empty string, if the DN is not specified, or if no credentials are specified, the server assumes that the client is binding anonymously and sends an LDAP_SUCCESS result code back to the client.
The DN and authentication method for the connection, which are used to determine access rights for all operations performed through the connection, are left as NULL and SLAPD_AUTH_NONE, respectively.
  1. If the DN specified in the request is not served by this Directory Server (for example, if the DN is uid=moxcross,dexample,dc=com, and the directory root of the server is dc=example,dc=com), the server sends one of the following two results back to the client and ends the processing of the bind request:
    • If the server is configured with a default referral (an LDAP URL identifying an LDAP server that handles referrals), the server sends an LDAP_REFERRAL result code back to the client (LDAP_PARTIAL_RESULTS if the client only supports the LDAPv2 protocol).
    • If the server is not configured with a default referral, the server sends an LDAP_NO_SUCH_OBJECT result code back to the client.
  2. The server puts the information from the bind request into the parameter block:
    • SLAPI_BIND_TARGET is set to the DN as which the client is authenticating.
    • SLAPI_BIND_METHOD is set to the authentication method (for example, LDAP_AUTH_SIMPLE or LDAP_AUTH_SASL).
    • SLAPI_BIND_CREDENTIALS is set to the credentials (for example, the password) included in the request.
    • SLAPI_BIND_SASLMECHANISM (if the authentication method is LDAP_AUTH_SASL) is set to the name of the SASL mechanism that the client is using for authentication.
  3. If the DN is the root DN or the update DN (the DN of the master entity responsible for replicating the directory), the server authenticates the client.
    • If the credentials are correct, the server sets the SLAPI_CONN_DN parameter to the DN and the SLAPI_CONN_AUTHTYPE parameter to LDAP_AUTH_SIMPLE. The server sends an LDAP_SUCCESS result code back to the client and ends the processing of the bind request.
    • If the credentials are incorrect, the server sends an LDAP_INVALID_CREDENTIALS result code back to the client and ends the processing of the bind request.
  4. The server calls any pre-operation bind plug-in functions. If the function returns a non-zero value, the server ends the processing of the bind request.
If you are writing your own plug-in function to handle authentication, you should return a non-zero value so that the server does not attempt to continue processing the bind request.
  1. The server calls the backend bind function. The bind function returns one of the following values:
    • If the function returns a non-zero value, the server ends the processing of the bind request. The bind function is responsible for sending the appropriate result code back to the client before returning a non-zero value.
    • If the function returns 0, the server continues processing the bind request. The server sends the LDAP_SUCCESS result code back to the client. (The bind function does not do this.)
  2. If the backend bind function succeeds, the server sets the SLAPI_CONN_DN parameter to the DN and the SLAPI_CONN_AUTHTYPE parameter to the authentication method.
  3. The server sends an LDAP_SUCCESS result code back to the client and ends the processing of the bind request.
If the client's password is going to expire, the server includes a password expiring control (with the OID 2.16.840.1.113730.3.4.5) with the result sent to the client. If the client is logging in for the first time and needs to change the password, the server includes a password expired control (with the OID 2.16.840.1.113730.3.4.4) with the result sent to the client.

Writing Your Own Authentication Plug-in

A situation may arise in which you may want to write and implement your own function for authentication; that is, replace the standard means of authentication with your own function. You can write a pre-operation bind plug-in function (a function that the server calls before processing an LDAP bind request) that performs the authentication and bypasses the default bind functionality. For details, see the next section, "Writing a Pre-Operation Bind Plug-in," on page 103.

Writing a Pre-Operation Bind Plug-in

You can define your own pre-operation bind plug-in function to authenticate LDAP clients. The server will call your function during the authentication process (in Step 9). Your function should return a non-zero value to bypass the default backend bind function and the post-operation bind functions.

This means that Step 10 through Step 12 are skipped. Your pre-operation plug-in function is responsible for sending the result code to the client and for setting the DN and authentication method for the connection.

Figure 8-1 summarizes the process of using a pre-operation bind plug-in function to authenticate LDAP clients to the Directory Server.

Figure 8-1 Using a Pre-Operation Bind Plug-in Function to Handle Authentication

Figure 8-2 illustrates the steps that your pre-operation bind plug-in function must take to authenticate LDAP clients to the Directory Server.

Figure 8-2 How Your Pre-Operation Bind Plug-in Function Can Authenticate LDAP Clients

Defining Your Authentication Function

The following sections cover guidelines that you can follow when defining your authentication function:

Note

Be sure to check this source file for an example of a pre-operation plug-in function that handles authentication: server_root /plugins/slapd/slapi/examples/testbind.c


Getting and Checking the Bind Parameters

Call the slapi_pblock_get() function to get the values of the following parameters:

If you plan to support authentication through SASL mechanisms, you should also get the value of the SLAPI_BIND_SASLMECHANISM parameter (a string value specifying the name of the SASL mechanism to use for authentication).

Make sure to check the following:

If the SLAPI_BIND_METHOD parameter is LDAP_AUTH_SIMPLE and the SLAPI_BIND_CREDENTIALS parameter is empty or NULL, the client is attempting to bind anonymously.
Call slapi_send_ldap_result() to send the LDAP result code LDAP_SUCCESS back to the client.

In both cases, return a non-zero value to prevent the server from calling the default backend function for authentication.

Getting the Entry and Checking the Credentials

Get the entry for the DN specified by the SLAPI_BIND_TARGET parameter, and compare the credentials in the SLAPI_BIND_CREDENTIALS parameter against the known credentials for that entry.

By default, Directory Server uses the userPassword attribute to store the credentials for an entry. The server encodes the password using the scheme specified in the nsslapd-rootpwstoragescheme or passwordStorageScheme attributes of the cn=config entry contained in the dse.ldif file. The scheme can be any of the following:

If you need to compare the client's credentials against the value of the userPassword attribute, you can call the slapi_pw_find_sv() function. This function determines which password scheme was used to store the password and uses the appropriate comparison function to compare a given value against the encrypted value of the userPassword attribute.

What to Do If Authentication Fails

If authentication fails, send one of the following result codes back to the client:

When calling the slapi_send_ldap_result() function to send the result code back to the client, specify the closest matching DN as the matched argument.

Your function should also return a non-zero value.

You do not need to set any values for the SLAPI_CONN_DN parameter and the SLAPI_CONN_AUTHTYPE parameter. By default, these parameters are set to NULL and LDAP_AUTH_NONE, which indicate that the client has bound anonymously.

What to Do If Authentication Succeeds

If the authentication is successful, your authentication function should do the following:

This sets the DN and authentication method for the connection to the client. (The server uses this DN and method in subsequent operations when checking access rights.)
You can set SLAPI_CONN_AUTHTYPE to one of the following values:
  • SLAPD_AUTH_NONE represents no authentication. (The client is binding anonymously.)
  • SLAPD_AUTH_SIMPLE represents the simple authentication method.
  • SLAPD_AUTH_SSL represents authentication through SSL.
  • SLAPD_AUTH_SASL represents SASL authentication.
These values differ from the values in the SLAPI_BIND_METHOD parameter. The values listed above are string values defined in the slapi-plugin.h header file, whereas the values of the SLAPI_BIND_METHOD parameter (such as LDAP_AUTH_SIMPLE and LDAP_AUTH_SASL) are integer values defined in the ldap.h header file.
If the value of the SLAPI_BIND_METHOD parameter is LDAP_AUTH_SASL and you want to return a set of credentials to the client, call slapi_pblock_set() to set the SLAPI_BIND_RET_SASLCREDS parameter to the credentials.
Call slapi_send_ldap_result() to send an LDAP_SUCCESS return code to the client.

Make sure that your function returns a non-zero value to bypass the default backend bind function and any post-operation plug-in functions.

Registering the SASL Mechanism

If you are using SASL as the authentication method, you need to register the SASL mechanisms that you plan to use.

In your initialization function (see "Writing Plug-in Initialization Functions," on page 42), call the Functions for Syntax Plug-in function and specify the name of the SASL mechanism. For example:

slapi_register_supported_saslmechanism( "babsmechanism" );
 

If you do not register your SASL mechanism, the Directory Server will send an LDAP_AUTH_METHOD_NOT_SUPPORTED result code back to the client and will not call your pre-operation bind function.

Note

Be sure to check this source file for an example of a pre-operation plug-in function for SASL authentication with LDAP bind operations: server_root /plugins/slapd/slapi/examples/testsaslbind.c


Example of a Pre-Operation Bind Plug-in

The following sections document an example of a pre-operation bind plug-in that handles authentication:

Note

Be sure to check this source file for an updated example of a pre-operation plug-in function that handles authentication: server_root /plugins/slapd/slapi/examples/testbind.c


Example of a Pre-Operation Bind Function

The following is an example of a pre-operation bind function that authenticates clients and bypasses the default backend bind function. In this example, the function just compares the client's credentials against the value of the userpassword attribute for the entry.

Code Example 8-1 Sample Pre-Operation Bind Function  
 
#include <stdio.h>
#include <string.h>
#include "slapi-plugin.h"
/* Pre-operation plug-in function */
int
test_bind( Slapi_PBlock *pb )
{
  char    *dn;
  int    method, rc = LDAP_SUCCESS;
  struct berval  *credentials;
  struct berval  **pwvals;
  Slapi_PBlock  *searchpb = NULL;
  Slapi_Entry    *e = NULL;
  Slapi_Entry    **entries = NULL;
  Slapi_Attr    *attr = NULL;
  /* Log a message to the server error log. */
  slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
    "Pre-operation bind function called.\n" );
  /* Gets parameters available when processing an LDAP bind
   operation. */
  if ( slapi_pblock_get( pb, SLAPI_BIND_TARGET, &dn ) != 0 ||
   slapi_pblock_get( pb, SLAPI_BIND_METHOD, &method ) != 0 ||
   slapi_pblock_get( pb, SLAPI_BIND_CREDENTIALS, &credentials ) != 0 ) {
    slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
      "Could not get parameters for bind operation\n" );
    slapi_send_ldap_result( pb, LDAP_OPERATIONS_ERROR,
      NULL, NULL, 0, NULL );
    return( 1 );
  }
  /* Check the authentication method */
  switch( method ) {
  case LDAP_AUTH_SIMPLE:
    /* First, get the entry specified by the DN. */
    searchpb = slapi_search_internal( dn, LDAP_SCOPE_BASE,
      "(objectclass=*)", NULL, NULL, 0 );
    if ( searchpb != NULL ) {
      slapi_pblock_get( pb, SLAPI_PLUGIN_INTOP_RESULT, &rc );
      if ( rc == LDAP_SUCCESS ) {
        slapi_pblock_get( searchpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
          &entries );
        if ( entries != NULL && entries[0] != NULL ) {
          e = entries[0];
        } else {
          slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
            "Could not find entry for %s\n", dn );
          rc = LDAP_NO_SUCH_OBJECT;
          break;
        }
      } else {
        slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
          "Could not find entry for %s (error %d)\n", dn, rc );
        break;
      }
    } else {
      slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
        "Could not search for entry\n" );
      rc = LDAP_OPERATIONS_ERROR;
      break;;
    }
    /* Next, check the credentials against the userpassword attribute
     of that entry. */
    if ( e != NULL ) {
      if ( slapi_entry_attr_find( e, "userpassword", &attr ) != 0 ) {
        slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
          "Entry has no userpassword attribute\n" );
        rc = LDAP_INAPPROPRIATE_AUTH;
        break;
      }
      slapi_attr_get_values( attr, &pwvals );
      if ( slapi_pw_find( pwvals, credentials ) != 0 ) {
        slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
          "Credentials are not correct for the entry\n" );
        rc = LDAP_INVALID_CREDENTIALS;
        break;
      }
    } else {
      /* This should not happen. The previous section of code
       already checks for this case. */
      slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
        "Could find entry for %s\n", dn );
      rc = LDAP_NO_SUCH_OBJECT;
      break;
    }
    /* Set the DN and the authentication method for the connection. */
    if ( slapi_pblock_set( pb, SLAPI_CONN_DN, slapi_ch_strdup( dn ) ) != 0 ||
      slapi_pblock_set( pb, SLAPI_CONN_AUTHTYPE, SLAPD_AUTH_SIMPLE ) != 0 ) {
      slapi_log_error( SLAPI_LOG_PLUGIN, "testbind_init",
        "Failed to set DN and auth method for connection\n" );
      rc = LDAP_OPERATIONS_ERROR;
      break;
    }
    /* Send a "success" result code back to the client. */
    slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind", "Authenticated: %s\n", dn );
    rc = LDAP_SUCCESS;
    break;
  /* If NONE is specified, the client is requesting to bind anonymously.
   Normally, this case should be handled by the server's front-end
   before it calls this plug-in function. Just in case this does
   get through to the plug-in function, you can handle this by
   sending a successful result code back to the client and returning
   1.
   */
  case LDAP_AUTH_NONE:
    slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind", "Authenticating anonymously\n" );
    rc = LDAP_SUCCESS;
    break;
  /* This plug-in does not support any other method of authentication */
  case LDAP_AUTH_SASL:
  default:
    slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
      "Unsupported authentication method requested: %d\n",
      method );
    rc = LDAP_AUTH_METHOD_NOT_SUPPORTED;
    break;
  }
  if ( searchpb ) {
    slapi_free_search_results_internal( searchpb );
    slapi_ch_free( ( void ** )&searchpb );
  }
  slapi_send_ldap_result( pb, rc, NULL, NULL, 0, NULL );
  return( 1 );
}
 

Example of an Initialization Function

To initialize your plug-in, write an initialization function to do the following:

The following is an example of an initialization function that registers the pre-operation bind function.

Code Example 8-2 Sample Function for Registering Pre-Operation Bind Function  
 
#include <stdio.h>
#include <string.h>
#include "slapi-plugin.h"
Slapi_PluginDesc bindpdesc = { "test-bind", "Red Hat", "0.5",
  "sample bind pre-operation plugin" };
/* Initialization function */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
testbind_init( Slapi_PBlock *pb )
{
  /* Register the pre-operation bind function and specify
   the server plug-in version. */
  if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,
    SLAPI_PLUGIN_VERSION_01 ) != 0 ||
    slapi_pblock_set( pb, SLAPI_PLUGIN_DESCRIPTION,
    (void *)&bindpdesc ) != 0 ||
    slapi_pblock_set( pb, SLAPI_PLUGIN_PRE_BIND_FN,
    (void *) test_bind ) != 0 ) {
    slapi_log_error( SLAPI_LOG_PLUGIN, "testbind_init",
      "Failed to set version and function\n" );
    return( -1 );
  }
  return( 0 );
}
 

Registering the Plug-in

To register the plug-in, add this to the end of the dse.ldif file, which is located in the server_root /slapd-instance_id /config directory:

dn: cn=Test Bind,cn=plugins,cn=config
objectClass: top
objectClass: nsSlapdPlugin
objectClass: extensibleObject
cn: Test Bind
nsslapd-pluginPath:
server_root /plugins/slapd/slapi/examples/libtest-plugin.so
nsslapd-pluginInitfunc: testbind_init
nsslapd-pluginType: preoperation
nsslapd-pluginEnabled: on
nsslapd-plugin-depends-on-type: database
nsslapd-pluginId: test-bind

Check this source file for an example of a pre-operation plug-in function that handles authentication:

server_root /plugins/slapd/slapi/examples/testbind.c
 

Using SASL with an LDAP Client

If you intend to use SASL as the method for authenticating clients, you need to enable your LDAP clients to use SASL.

In your client, call the ldap_sasl_bind() or ldap_sasl_bind_s() function to request authentication using SASL. To parse credentials from an asynchronous SASL bind operation, call ldap_parse_sasl_bind_result(). These functions are part of LDAP C SDK 3.0.

The syntax for these functions are listed below:

LDAP_API(int) LDAP_CALL ldap_sasl_bind( LDAP *ld, const char 
*dn, 

  const char *mechanism, struct berval *cred, 

  LDAPControl **serverctrls, LDAPControl **clientctrls, int 
*msgidp );
 
LDAP_API(int) LDAP_CALL ldap_sasl_bind_s( LDAP *ld, const char 
*dn, 

  const char *mechanism, struct berval *cred, 

  LDAPControl **serverctrls, LDAPControl **clientctrls, 

  struct berval **servercredp );
 

The parameters are described below:

If you are call the ldap_sasl_bind() function, you need to call the ldap_result() function to get the result of the authentication attempt:

  LDAP_API(int) LDAP_CALL ldap_result( LDAP *ld, int msgid, \

    int all, struct timeval *timeout, LDAPMessage **result );
 

The parameters are described below:

After calling ldap_result(), you need to call the ldap_parse_sasl_bind_result() function to parse the results and retrieve the LDAP result code and any credentials returned by the server:

  LDAP_API(int) LDAP_CALL ldap_parse_sasl_bind_result( LDAP *ld, \

  LDAPMessage *res, struct berval **servercredp, int freeit);
 

The parameters are described below:

The following example is an LDAP client that authenticates using the SASL method named babsmechanism.

Code Example 8-3 LDAP Client Authenticating Using SASL Method  
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ldap.h>
 
int
main( int argc, char **argv )
{
  LDAP  *ld;
  LDAPMod  mod0;
  LDAPMod  mod1;
  LDAPMod  *mods[ 3 ];
  char  *vals0[ 2 ];
  char  *vals1[ 2 ];
  time_t  now;
  char  buf[ 128 ];
  struct berval cred;
  struct berval *servcred;
  int version;
  /* get a handle to an LDAP connection */
  if ( (ld = ldap_init( "localhost", 389 )) == NULL ) {
  perror( "ldap_init" );
  return( 1 );
  }
  /* Set the LDAP protocol version supported by the client
  to 3. (By default, this is set to 2. Extended operations
  are part of version 3 of the LDAP protocol.) */
  version = LDAP_VERSION3;
  ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
  /* authenticate */
  cred.bv_val = "magic";
  cred.bv_len = sizeof( "magic" ) - 1;
  if ( ldap_sasl_bind_s( ld, "uid=bjensen,ou=people,l=US, dc=example,dc=com", "babsmechanism", &cred, NULL, NULL, &servcred ) != LDAP_SUCCESS ) {
  ldap_perror( ld, "ldap_sasl_bind_s" );
  return( 1 );
  }
  /* get and print the credentials returned by the server */
  printf( "Server credentials: %s\n", servcred->bv_val );
  /* construct the list of modifications to make */
  mod0.mod_op = LDAP_MOD_REPLACE;
  mod0.mod_type = "mail";
  vals0[0] = "[email protected]";
  vals0[1] = NULL;
  mod0.mod_values = vals0;
  mod1.mod_op = LDAP_MOD_ADD;
  mod1.mod_type = "description";
  time( &now );
  sprintf( buf, "This entry was modified with the modattrs program on %s",
    ctime( &now ));
  /* Get rid of \n which ctime put on the end of the time string */
  if ( buf[ strlen( buf ) - 1 ] == '\n' ) {
  buf[ strlen( buf ) - 1 ] = '\0';
  }
vals1[ 0 ] = buf;
vals1[ 1 ] = NULL;
mod1.mod_values = vals1;
mods[ 0 ] = &mod0;
mods[ 1 ] = &mod1;
mods[ 2 ] = NULL;
/* make the change */
if ( ldap_modify_s( ld, "uid=bjensen,ou=people,l=US, dc=example,dc=com", mods )
   != LDAP_SUCCESS ) {
  ldap_perror( ld, "ldap_modify_s" );
  return( 1 );
}
ldap_unbind( ld );
printf( "modification was successful\n" );
return( 0 );
}
 




Previous
Contents
Index
Next

© 2001 Sun Microsystems, Inc. Used by permission. © 2005 Red Hat, Inc. All rights reserved.
Read the Full Copyright and Third-Party Acknowledgments.

last updated May 26, 2005