8.5. 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. See Procedure 8.1, “How an Authentication Request is Processed” for more information on the authentication process. Your function should return a non-zero value to bypass the default backend bind function and the post-operation bind functions.
This means that the final steps of the authentication process 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, “Using a Pre-Operation bind Plug-in Function to Handle Authentication” summarizes the process of using a pre-operation bind plug-in function to authenticate LDAP clients to the Directory Server.
Using a Pre-Operation bind Plug-in Function to Handle Authentication

Figure 8.1. Using a Pre-Operation bind Plug-in Function to Handle Authentication

Figure 8.2, “How Your Pre-Operation Bind Plug-in Function Can Authenticate LDAP Clients” illustrates the steps that your pre-operation bind plug-in function must take to authenticate LDAP clients to the Directory Server.
How Your Pre-Operation Bind Plug-in Function Can Authenticate LDAP Clients

Figure 8.2. How Your Pre-Operation Bind Plug-in Function Can Authenticate LDAP Clients

8.5.1. Defining the Authentication Function

Note

Check out the sample testbind.c source file as an example of a pre-operation plug-in function that handles authentication. This file is in the /usr/lib64/dirsrv/plugins/test-plugins/ directory.
Sample plug-in files are installed separately from other Directory Server packages, available at the 389 Directory Server repos, http://git.fedorahosted.org/cgit/389/ds.git/tree/ldap/servers/plugins and http://git.fedorahosted.org/cgit/389/ds.git/tree/ldap/servers/slapd/test-plugins. These sample plug-in files can be installed in any directory.

8.5.1.1. Getting and Checking the Bind Parameters

Call the slapi_pblock_get() function to get the values of the following parameters:
  • SLAPI_BIND_TARGET - A string value specifying the DN as which the client is attempting to authenticate.
  • SLAPI_BIND_METHOD - An integer value specifying the authentication method, such as LDAP_AUTH_SIMPLE or LDAP_AUTH_SASL.
  • SLAPI_BIND_CREDENTIALS - A berval structure containing the credentials sent by the client.
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).
To handle the bind operation entirely within the plug-in, with no further frontend or backend processing, do the following:
  • Determine if the client is requesting to bind as an anonymous user.
    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. Alternatively, disallow an anonymous bind and return the LDAP result code LDAP_UNWILLING_TO_PERFORM.
    Call slapi_send_ldap_result() to send the LDAP result code LDAP_SUCCESS back to the client.
  • If the SLAPI_BIND_METHOD parameter specifies a method that you do not recognize or support, call slapi_send_ldap_result() to send an LDAP_STRONG_AUTH_NOT_SUPPORTED result code back to the client.
In both cases, return a non-zero value as the plug-in function return value to prevent the server from calling the default backend function for authentication.

8.5.1.2. 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. In order to get the entry, you must perform an internal search. There are several functions that can be used, listed in order of increasing power and complexity:
Then, it is possible to use the attribute and value functions listed in Table 5.1, “Frontend Functions for Manipulating Entries and Attributes” to get the values.
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 attribute for the Directory Manager or passwordStorageScheme attribute for other users. These attributes are defined in the cn=config entry contained in the dse.ldif file. The scheme can be any of the following:
  • CLEAR — No encryption is used, and can be defined using the clear-password-storage-scheme plug-in.
  • CRYPT — Uses the Unix crypt algorithm, and can be defined using the crypt-password-storage-scheme plug-in.
  • SHA, SHA256, SHA384, SHA512 — Uses the Secure Hashing Algorithm, and can be defined using the sha-password-storage-scheme plug-in. SHA is SHA-1, which is 140 bits. For the others, the number indicates the number of bits used by the hash.
  • SSHA, SSHA256, SSHA384, SSHA512 — Uses the Salted Secure Hashing Algorithm, and can be defined using the ssha-password-storage-scheme plug-in. SSHA is SSHA-1, which is 140 bits, including the salt. For the others, the number indicates the number of bits used by the hash, including the salt.
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.

8.5.1.3. What to Do If Authentication Fails

If authentication fails, send one of the following result codes back to the client:
  • If no entry matches the DN specified by the client, send an LDAP_NO_SUCH_OBJECT result code 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.
  • If the client fails to provide the necessary credentials, or if credentials cannot be found in the entry, send an LDAP_INAPPROPRIATE_AUTH result code back to the client.
  • If the credentials specified by the client do not match the credentials found in the entry, send an LDAP_INVALID_CREDENTIALS result code back to the client.
  • If a general error occurs, send an LDAP_OPERATIONS_ERROR result code back to the client.
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.

8.5.1.4. What to Do If Authentication Succeeds

If the authentication is successful, your authentication function should:
  • Call slapi_pblock_set() to set the values of the SLAPI_CONN_DN parameter and the SLAPI_CONN_AUTHTYPE parameter to the DN and authentication method.
    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 TLS.
    • 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 required, specify the credentials that you want sent back to the client.
    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.
  • Send the result of the authentication process back to the client.
    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.

8.5.2. 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 Section 2.2, “Writing Plug-in Initialization Functions”), call the slapi_register_supported_saslmechanism() 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

Check out the sample testsaslbind.c source file as an example of a pre-operation plug-in function for SASL authentication. This file is in the /usr/lib64/dirsrv/plugins/test-plugins/ directory.
Sample plug-in files are installed separately from other Directory Server packages, available at the 389 Directory Server repos, http://git.fedorahosted.org/cgit/389/ds.git/tree/ldap/servers/plugins and http://git.fedorahosted.org/cgit/389/ds.git/tree/ldap/servers/slapd/test-plugins. These sample plug-in files can be installed in any directory.

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

Check out the sample testbind.c source file as an example of a pre-operation plug-in function that handles authentication. This file is in the /usr/lib64/dirsrv/plugins/test-plugins/ directory.
Sample plug-in files are installed separately from other Directory Server packages, available at the 389 Directory Server repos, http://git.fedorahosted.org/cgit/389/ds.git/tree/ldap/servers/plugins and http://git.fedorahosted.org/cgit/389/ds.git/tree/ldap/servers/slapd/test-plugins. These sample plug-in files can be installed in any directory.

8.5.3.1. 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 compares the client's credentials against the value of the userpassword attribute for the entry.
#include <stdio.h>
#include <string.h>
#include "dirsrv/slapi-plugin.h"

/* Pre-operation plug-in function */

int 
test_bind(Slapi_PBlock *pb )
{
    int method, rc = LDAP_SUCCESS;
    struct berval *credentials;
    Slapi_Entry *e = NULL;
    Slapi_Attr *attr = NULL;
    Slapi_ValueSet *vs = NULL;
    Slapi_Value *sv_creds = NULL;
    Slapi_Value **sva = NULL;
    Slapi_DN *sdn = NULL;
    const char *dn = NULL;
    /* we only care about these attributes */
    char *attrlist[] = { "userPassword", NULL };
    void *my_plugin_id = 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_SDN, &sdn ) != 0 ||
         slapi_pblock_get( pb, SLAPI_BIND_METHOD, &method ) != 0 ||
	 slapi_pblock_get( pb, SLAPI_BIND_CREDENTIALS, &credentials ) != 0 || 
	 slapi_pblock_get( pb, SLAPI_PLUGIN_IDENTITY, &my_plugin_id) != 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 );
    }

    sv_creds = slapi_value_new_berval(credentials); /* wrap in Slapi_Value* */
    dn = slapi_sdn_get_dn(sdn);

    /* Check the authentication method */
    switch( method ) {
        case LDAP_AUTH_SIMPLE:
	    /* First, get the entry specified by the DN. */
	    rc = slapi_search_internal_get_entry(sdn, attrlist, &e, my_plugin_id);
	    if ((LDAP_SUCCESS == rc) && (NULL != e)) {
	        Slapi_Value *val = NULL;
		int num_vals = 0;
		int hint = 0;
		int i = 0;

		/* see if the entry has the userpassword attribute */
		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_valueset( attr, &vs ); /* must free vs */
		slapi_attr_get_numvalues(attr, &num_vals);
		sva = (Slapi_Value **) slapi_ch_calloc( (num_vals + 1), sizeof(Slapi_Value *));

		/* Loop through all of our values s and create a value array */
		hint = slapi_valueset_first_value(vs, &val);
		while (val)
		{
		    sva[i] = val;
		    i++;
		    hint = slapi_valueset_next_value(vs, hint, &val);
		}

		/* Next, check the credentials against the userpassword attribute of that entry. */
		if ( slapi_pw_find_sv( sva, sv_creds ) != 0 ) {
		    slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind" ,
		            "Credentials are not correct for the entry\n" );
                    rc = LDAP_INVALID_CREDENTIALS;
		    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_AUTHMETHOD, 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;
            } else { /* error code or no entry */
	        slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind",
		        "Could not find entry for %s: Error: %s\n", 
			dn, (rc == LDAP_SUCCESS) ? "unknown" : ldap_err2string(rc) );
		/* if the entry was null, there was probably an internal error */
		if (LDAP_SUCCESS == rc) {
		    rc = LDAP_OPERATIONS_ERROR;
		}

            }
	    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,
	 * or if you do not want to support anon, return LDAP_UNWILLING_TO_PERFORM  
	 */
	case LDAP_AUTH_NONE:
	    slapi_log_error( SLAPI_LOG_PLUGIN, "test_bind" , "Authenticating anonymously\n" );
	    rc = LDAP_SUCCESS; /* or return LDAP_UNWILLING_TO_PERFORM if anon not supported */
	    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;
    }

    /* clean up - ok to pass NULL to these */
    slapi_entry_free(e);
    slapi_valueset_free(vs);
    slapi_ch_free((void **)&sva);
    slapi_value_free(&sv_creds);
    slapi_sdn_free(&sdn);

    /* actually return the result to the client */
    slapi_send_ldap_result( pb, rc, NULL, NULL, 0, NULL );

    return( rc );
}

8.5.3.2.  Example of an Initialization Function

To initialize your plug-in, write an initialization function to:
The following is an example of an initialization function that registers the pre-operation bind function.
#include <stdio.h>
#include <string.h>
#include "dirsrv/slapi-plugin.h"

Slapi_PluginDesc bindpdesc = { "test-bind" , "Red Hat" , "0.5" ,"sample bind pre-operation plugin" };

/* our plug-in identity . set in init function */
static Slapi_ComponentId *my_plugin_identity;

/* Initialization function */
#ifdef _WIN32
__declspec(dllexport)
#endif

int
testbind_init( Slapi_PBlock *pb )
{
    /*
     * Get our plug-in identity . we will need this to perform
     * any internal operations (search, modify, etc.) 
     */
    slapi_pblock_get (pb, SLAPI_PLUGIN_IDENTITY, &my_plugin_identity);

    /* 
     * Register the pre-operation bind function and specify
     * the server plug-in version.
     */
    if ( slapi_pblock_set( pb, SLAPI_PLUGIN_VERSION,SLAPI_PLUGIN_VERSION_03 ) != 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 );
}

8.5.3.3. Registering the Plug-in

To register the plug-in, add the following to the end of the /etc/dirsrv/slapd-instance/dse.ldif file:
dn: cn=Test Bind,cn=plugins,cn=config
objectClass: top
objectClass: nsSlapdPlugin
objectClass: extensibleObject
cn: Test Bind
nsslapd-pluginPath:/path/to/test-plugin.so   
nsslapd-pluginInitfunc: testbind_init
nsslapd-pluginType: preoperation
nsslapd-pluginEnabled: on
nsslapd-plugin-depends-on-type: database
nsslapd-pluginId: test-bind
Check out the sample testbind.c source file as an example of a pre-operation plug-in function that handles authentication. This file is in the /usr/lib64/dirsrv/plugins/test-plugins/ directory.

Note

Sample plug-in files are installed separately from other Directory Server packages, available at the 389 Directory Server repos, http://git.fedorahosted.org/cgit/389/ds.git/tree/ldap/servers/plugins and http://git.fedorahosted.org/cgit/389/ds.git/tree/ldap/servers/slapd/test-plugins. These sample plug-in files can be installed in any directory.
There are also examples in the source code itself. Look in the /usr/lib64/dirsrv/plugins directory for plug-ins that implement SLAPI_PLUGIN_PRE_BIND_FN.
The example code given is very basic. There are many other things which a bind plug-in could do. For example:
  • Log the authentication attempt to the access log for auditing.
  • Check for password expiration and use slapi_add_pwd_control() to send that information back to the client.
  • See if the client has requested additional password policy information in a couple of different ways:
    slapi_pblock_get (pb, SLAPI_REQCONTROLS, ...) 
    slapi_pblock_get (pb, SLAPI_PWPOLICY, ...)
    Then send the requested information back to the client using slapi_pwpolicy_make_response_control().
  • Manage other aspects of password policy.
Finally, take a look at the server bind code in bind.c to see what sort of processing it does.