Sun Java System Directory Server Enterprise Edition 6.2 Developer's Guide

Developing a Simple Authentication Plug-In

This section shows how a preoperation bind plug-in can use the plug-in API to authenticate a user. The example used in this section obtains the appropriate bind information from the parameter block. The example then handles the authentication if the request is for LDAP_AUTH_SIMPLE, but allows the server to continue the bind if the authentication succeeds.

Notice that some binds are performed by the front end before preoperation bind functions are called.

Locating the Simple Authentication Example

The following example shows a code excerpt from the source file install-path/examples/testbind.c.


Example 7–1 Preoperation Bind Function (testbind.c)

#include "slapi-plugin.h"

int
test_bind(Slapi_PBlock * pb)
{
    char          *  dn;               /* Target DN                 */
    int              method;           /* Authentication method     */
    struct berval *  credentials;      /* Client SASL credentials   */
    Slapi_DN      *  sdn      = NULL;  /* DN used in internal srch  */
    char          *  attrs[2] = {      /* Look at userPassword only */
                                SLAPI_USERPWD_ATTR,
                                NULL
                     };
    Slapi_Entry   *  entry    = NULL;  /* Entry returned by srch    */
    Slapi_Attr    *  attr     = NULL;  /* Pwd attr in entry found   */
    int              is_repl;          /* Is this replication?      */
    int              is_intl;          /* Is this an internal op?   */
    int              connId, opId, rc = 0;
    long             msgId;
        
    /* Obtain the bind information from the parameter block.        */
    rc |= slapi_pblock_get(pb, SLAPI_BIND_TARGET,             &dn);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_METHOD,             &method);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS,        &credentials);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID,         &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,                 &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,            &opId);
    rc |= slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &is_repl);
    rc |= slapi_pblock_get(pb, SLAPI_IS_INTERNAL_OPERATION,   &is_intl);

    if (rc != 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            SLAPI_LOG_NO_MSGID,
            SLAPI_LOG_NO_CONNID,
            SLAPI_LOG_NO_OPID,
            "test_bind in test-bind plug-in",
            "Could not get parameters for bind operation (error %d).\n", rc
        );
        slapi_send_ldap_result(
            pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        return (LDAP_OPERATIONS_ERROR);/* Server aborts bind here.  */
    }

    /* The following code handles simple authentication, where a
     * user offers a bind DN and a password for authentication.
     *
     * Handling simple authentication is a matter of finding the
     * entry corresponding to the bind DN sent in the request,
     * then if the entry is found, checking whether the password
     * sent in the request matches a value found on the
     * userPassword attribute of the entry.                         */

    /* Avoid interfering with replication or internal operations.   */
    if (!is_repl && !is_intl) switch (method) {
    case LDAP_AUTH_SIMPLE:

        /* Find the entry specified by the bind DN...               */
        sdn = slapi_sdn_new_dn_byref(dn);
        rc |= slapi_search_internal_get_entry(
            sdn,
            attrs,
            &entry,
            plugin_id
            );
        slapi_sdn_free(&sdn);

        if (rc != 0 || entry == NULL) {
            slapi_log_info_ex(
                SLAPI_LOG_INFO_AREA_PLUGIN,
                SLAPI_LOG_INFO_LEVEL_DEFAULT,
                msgId,
                connId,
                opId,
                "test_bind in test-bind plug-in",
                "Could not find entry: %s\n", dn
            );
            rc = LDAP_NO_SUCH_OBJECT;
            slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
            return (rc);
        } else {
        /* ...check credentials against the userpassword...         */
            Slapi_Value    *  credval; /* Value of credentials      */
            Slapi_ValueSet * pwvalues; /* Password attribute values */
                        
            rc |= slapi_entry_attr_find(
                entry,
                SLAPI_USERPWD_ATTR,
                &attr
            );

            if (attr == NULL) {

                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    msgId,
                    connId,
                    opId,
                    "test_bind in test-bind plug-in",
                    "Entry %s has no userpassword.\n",
                    dn
                );
                rc = LDAP_INAPPROPRIATE_AUTH;
                slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
                return (rc);
            }

            rc |= slapi_attr_get_valueset(
                attr,
                &pwvalues
            );
            if (rc != 0 || slapi_valueset_count(pwvalues) == 0) {
                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    msgId,
                    connId,
                    opId,
                    "test_bind in test-bind plug-in",
                    "Entry %s has no %s attribute values.\n",
                    dn, SLAPI_USERPWD_ATTR
                );
                rc = LDAP_INAPPROPRIATE_AUTH;
                slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
                                slapi_valueset_free(pwvalues);
                                return (rc);
            }

            credval = slapi_value_new_berval(credentials);

            rc = slapi_pw_find_valueset(pwvalues, credval);

            slapi_value_free(&credval);
                        slapi_valueset_free(pwvalues);

            if (rc != 0) {
                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    msgId,
                    connId,
                    opId,
                    "test_bind in test-bind plug-in",
                    "Credentials are not correct.\n"
                );
                rc = LDAP_INVALID_CREDENTIALS;
                slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
                return (rc);
            }
        }

        /* ...if successful, set authentication for the connection. */
        if (rc != 0) return (rc);
        rc |=slapi_pblock_set(pb, SLAPI_CONN_DN, slapi_ch_strdup(dn));
        rc |=slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD, SLAPD_AUTH_SIMPLE);
        if (rc != 0) {
            slapi_log_info_ex(
                SLAPI_LOG_INFO_AREA_PLUGIN,
                SLAPI_LOG_INFO_LEVEL_DEFAULT,
                msgId,
                connId,
                opId,
                "test_bind in test-bind plug-in",
                "Failed to set connection info.\n"
            );
            rc = LDAP_OPERATIONS_ERROR;
            slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
            return (rc);
        } else {
            slapi_log_info_ex(
                SLAPI_LOG_INFO_AREA_PLUGIN,
                SLAPI_LOG_INFO_LEVEL_DEFAULT,
                msgId,
                connId,
                opId,
                "test_bind in test-bind plug-in",
                "Authenticated: %s\n", dn
            );

            /* Now that authentication succeeded, the plug-in
             * returns a value greater than 0, even though the
             * authentication has been successful. A return
             * code > 0 tells the server not to continue
             * processing the bind. A return code of 0, such
             * as LDAP_SUCCESS tells the server to continue
             * processing the operation.                            */
            slapi_send_ldap_result(
                pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
            rc = 1;
        }
        break;

    /* This plug-in supports only simple authentication.            */
    case LDAP_AUTH_NONE:
        /* Anonymous binds are handled by the front-end before
         * pre-bind plug-in functions are called, so this
         * part of the code should never be reached.                */
    case LDAP_AUTH_SASL:
    default:
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "test_bind in test-bind plug-in",
            "Plug-in does not handle auth. method: %d\n", method
        );
        rc = 0;                        /* Let server handle bind.   */
        break;
    }

    return (rc);                       /* Server stops processing
                                        * the bind if rc > 0.       */
}

The bulk of the code processes the LDAP_AUTH_SIMPLE case. In the simple authentication case, the plug-in uses the DN and the password to authenticate the user binding through plug-in API calls.

Seeing the Plug-In Work

The plug-in demonstration works by turning on informational logging for plug-ins. You read the log messages written by the plug-in at different stages in its operation. Before using the plug-in, load a few example users and data because you cannot demonstrate the functionality while binding as a directory superuser. without calling the preoperation bind functions.

ProcedureTo Set Up an Example Suffix

If you have not done so already, set up a directory instance with a suffix, dc=example,dc=com, containing data loaded from a sample LDIF file, install-path/ds6/ldif/Example.ldif.

  1. Create a new Directory Server instance.

    For example:


    $ dsadm create /local/ds
    Choose the Directory Manager password:
    Confirm the Directory Manager password:
    $ 
  2. Start the new Directory Server instance.

    For example:


    $ dsadm start /local/ds
    Server started: pid=4705
    $ 
  3. Create a suffix called dc=example,dc=com.

    For example, with long lines folded for the printed page:


    $ dsconf create-suffix -h localhost -p 1389 dc=example,dc=com
    Enter "cn=directory manager" password: 
    Certificate "CN=defaultCert, CN=hostname:1636" presented by the
     server is not trusted.
    Type "Y" to accept, "y" to accept just once,
     "n" to refuse, "d" for more details: Y
    $ 
  4. Load the sample LDIF.

    For example, with long lines folded for the printed page:


    $ dsconf import -h localhost -p 1389 \
     /opt/SUNWdsee/ds6/ldif/Example.ldif dc=example,dc=com
    Enter "cn=directory manager" password:  
    New data will override existing data of the suffix
     "dc=example,dc=com".
    Initialization will have to be performed on replicated suffixes. 
    Do you want to continue [y/n] ? y
    
    ## Index buffering enabled with bucket size 16
    ## Beginning import job...
    ## Processing file "/opt/SUNWdsee/ds6/ldif/Example.ldif"
    ## Finished scanning file "/opt/SUNWdsee/ds6/ldif/Example.ldif" (160 entries)
    ## Workers finished; cleaning up...
    ## Workers cleaned up.
    ## Cleaning up producer thread...
    ## Indexing complete.
    ## Starting numsubordinates attribute generation.
     This may take a while, please wait for further activity reports.
    ## Numsubordinates attribute generation complete. Flushing caches...
    ## Closing files...
    ## Import complete. Processed 160 entries in 5 seconds.
     (32.00 entries/sec)
    
    Task completed (slapd exit code: 0).
    $ 
See Also

You can use Directory Service Control Center to perform this task. For more information, see the Directory Service Control Center online help.

ProcedureTo Register the Plug-In

If you have not already done so, build the example plug-in library and activate both plug-in informational logging and the example plug-in.

  1. Build the plug-in.

    Hint Use install-path/examples/Makefile or install-path/examples/Makefile64.

  2. Configure Directory Server to log plug-in informational messages and load the plug-in.

    Hint Use the commands specified in the comments at the outset of the plug-in source file.

  3. Restart Directory Server.


    $ dsadm restart instance-path
    

ProcedureTo Bypass the Plug-In

The example suffix contains a number of people. If you look up the entry for one of those people, Barbara Jensen, either anonymously or as Directory Manager, the test_bind() plug-in function is never called. The plug-in therefore never logs informational messages to the errors log.

  1. Run a search that bypasses the plug-in.


    $ ldapsearch -h localhost -p 1389 -b dc=example,dc=com uid=bjensen sn 
    version: 1
    dn: uid=bjensen, ou=People, dc=example,dc=com
    sn: Jensen
    $ grep test_bind /local/ds/logs/errors
    $

    Notice that the server bypasses preoperation bind plug-ins when special users request a bind.

ProcedureTo Bind as an Example.com User

  1. Check what happens in the errors log when you bind as Barbara Jensen.


    $ ldapsearch -h localhost -p 1389 -b dc=example,dc=com \
     -D uid=bjensen,ou=people,dc=example,dc=com -w hifalutin uid=bjensen sn
    version: 1
    dn: uid=bjensen, ou=People, dc=example,dc=com
    sn: Jensen
    $ grep test_bind /local/ds/logs/errors
    [04/Jan/2006:11:34:31 +0100] - INFORMATION -
     test_bind in test-bind plug-in
     - conn=4 op=0 msgId=1 - 
     Authenticated: uid=bjensen,ou=people,dc=example,dc=com
    $ 
  2. See what happens when you bind as Barbara Jensen, but get the password wrong.


    $ ldapsearch -h localhost -p 1389 -b dc=example,dc=com \
     -D uid=bjensen,ou=people,dc=example,dc=com -w bogus uid=bjensen sn
    ldap_simple_bind: Invalid credentials
    $ grep test_bind /local/ds/logs/errors | grep -i credentials
    [04/Jan/2006:11:36:07 +0100] - INFORMATION -
     test_bind in test-bind plug-in
     - conn=5 op=0 msgId=1 -  Credentials are not correct.
    $ 

    Here, the LDAP result is interpreted correctly by the command-line client. The plug-in message to the same effect is written to the errors log.

  3. Delete Barbara's password, then try again.


    $ ldapmodify -h localhost -p 1389 \
     -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery
    dn: uid=bjensen,ou=people,dc=example,dc=com
    changetype: modify
    delete: userpassword
    modifying entry uid=bjensen,ou=people,dc=example,dc=com
    ^D
    $ ldapsearch -h localhost -p 1389 -b dc=example,dc=com \
     -D uid=bjensen,ou=people,dc=example,dc=com -w - uid=bjensen sn
    Enter bind password: 
    ldap_simple_bind: Inappropriate authentication
    $ grep test_bind /local/ds/logs/errors | grep -i password
    [04/Jan/2006:11:41:25 +0100] - INFORMATION -
     test_bind in test-bind plug-in
     - conn=8 op=0 msgId=1 - 
     Entry uid=bjensen,ou=people,dc=example,dc=com has no userpassword.
    $ 

    Here, the LDAP result is displayed correctly by the command-line client. The plug-in message will provide more information about what went wrong during Barbara’s attempt to bind, no userpassword attribute values.