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

Developing a SASL Authentication Plug-In

This section shows how a pre-bind operation plug-in can implement a custom Simple Authentication and Security Layer (SASL) mechanism. This mechanism enables mutual authentication without affecting existing authentication mechanisms.

The example plug-in responds to SASL bind requests with a mechanism, my_sasl_mechanism. Binds with this mechanism always succeed. The example is intended only to illustrate how a SASL authentication plug-in works, not to provide a secure mechanism for use in production.

Locating SASL Examples

This overall SASL section covers the examples install-path/examples/testsaslbind.c and install-path/examples/clients/saslclient.c.

Before using the plug-in function as described here, set up an example suffix. The setup is described in Seeing the Plug-In Work. Register the plug-in as described in the comments at the beginning of the sample file.

Registering the SASL Mechanism

Register the SASL mechanism by using the same function that registers the plug-in, slapi_register_supported_saslmechanism(). The following example shows the function that registers both the plug-in and the SASL mechanism.


Example 7–2 Registering a Custom SASL Plug-In (testsaslbind.c)

#include "slapi-plugin.h"

Slapi_PluginDesc saslpdesc = {
    "test-saslbind",                   /* plug-in identifier          */
    "Sun Microsystems, Inc.",          /* vendor name                 */
    "6.0",                             /* plug-in revision number     */
    "Sample SASL pre-bind plug-in"     /* plug-in description         */
};

#define TEST_MECHANISM  "my_sasl_mechanism"
#define TEST_AUTHMETHOD SLAPD_AUTH_SASL TEST_MECHANISM

/* Register the plug-in with the server.                              */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
testsasl_init(Slapi_PBlock * pb)
{
    int rc = 0;                        /* 0 means success             */
    rc |= slapi_pblock_set(
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description         */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &saslpdesc
    );
    rc |= slapi_pblock_set(            /* Pre-op bind SASL function   */
        pb,
        SLAPI_PLUGIN_PRE_BIND_FN,
        (void *) testsasl_bind
    );
            
    /* Register the SASL mechanism.                                   */
    slapi_register_supported_saslmechanism(TEST_MECHANISM);
    return (rc);
}

Refer to Part II, Directory Server Plug-In API Reference for details on using slapi_register_supported_saslmechanism().

The plug-in function testsasl_bind() first obtains the client DN, method, SASL mechanism, and credentials. If the client does not ask to use the TEST_MECHANISM, the function returns 0 so the server can process the bind. The following example shows how the processing is done.


Example 7–3 Handling the my_sasl_mechanism Bind (testsaslbind.c)

#include "slapi-plugin.h"

#define TEST_MECHANISM  "my_sasl_mechanism"
#define TEST_AUTHMETHOD SLAPD_AUTH_SASL TEST_MECHANISM

int
testsasl_bind(Slapi_PBlock * pb)
{
    char          * dn;                /* Target DN                   */
    int             method;            /* Authentication method       */
    char          * mechanism;         /* SASL mechanism              */
    struct berval * credentials;       /* SASL client credentials     */
    struct berval   svrcreds;          /* SASL server credentials     */
    int             connId, opId, rc = 0;
    long            msgId;
        
    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_BIND_SASLMECHANISM, &mechanism);
    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);
    if (rc == 0) {
        if (mechanism == NULL || strcmp(mechanism, TEST_MECHANISM) != 0)
            return(rc);                /* Client binding another way. */
    } else {                           /* slapi_pblock_get() failed!  */
        return(rc);
    }

    /* Using the SASL mechanism that always succeeds, set conn. info. */
    rc |= slapi_pblock_set(pb, SLAPI_CONN_DN,         slapi_ch_strdup(dn));
    rc |= slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD, TEST_AUTHMETHOD);
    if (rc != 0) {                     /* Failed to set conn. info!   */
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testsasl_bind in test-saslbind plug-in",
            "slapi_pblock_set() for connection information failed.\n"
        );
        slapi_send_ldap_result(
            pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        return(LDAP_OPERATIONS_ERROR); /* Server tries other mechs.   */
    }

    /* Set server credentials.                                        */
    svrcreds.bv_val = "my credentials";
    svrcreds.bv_len = sizeof("my credentials") - 1;

    rc |= slapi_pblock_set(pb, SLAPI_BIND_RET_SASLCREDS, &svrcreds);
    if (rc != 0) {                     /* Failed to set credentials!  */
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testsasl_bind in test-saslbind plug-in",
            "slapi_pblock_set() for server credentials failed.\n"
        );
        rc |= slapi_pblock_set(pb, SLAPI_CONN_DN,         NULL);
        rc |= slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD, SLAPD_AUTH_NONE);
        return(LDAP_OPERATIONS_ERROR); /* Server tries other mechs.   */
    }
    
    /* Send credentials to client.                                    */
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testsasl_bind in test-saslbind plug-in",
        "Authenticated: %s\n", dn
    );
    slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
    return 1;                          /* Server stops processing the
                                        * bind if the plug-in returns
                                        * a value greater than 0.     */
}

The preceding example sets the DN for the client connection and the authentication method, as Directory Server does for a bind. To set SLAPI_CONN_DN and SLAPI_CONN_AUTHMETHOD is the key step of the bind. Without these values in the parameter block, Directory Server handles the client request as if the bind were anonymous. Thus, the client is left with access rights that correspond to an anonymous bind. Next, according to the SASL model, Directory Server sends credentials to the client as shown.

Developing the SASL Client

To test the plug-in, you need a client that uses my_sasl_mechanism to bind. The example discussed here uses this mechanism and the client code, saslclient.c, which is delivered with Directory Server.

The client authenticates by using the example SASL mechanism for a synchronous bind to Directory Server. The following example shows how authentication proceeds on the client side for Ted Morris.


Example 7–4 Client Using my_sasl_mechanism (clients/saslclient.c)

#include "ldap.h"

/* Use the fake SASL mechanism.                                      */
#define MECH "my_sasl_mechanism"

/* Global variables for client connection information.
 * You may set these here or on the command line.                    */
static char * host = "localhost";      /* Server hostname            */
static int    port = 389;              /* Server port                */
/* Load <install-path>/ldif/Example.ldif
 * before trying the plug-in with this default user.                 */
static char * user = "uid=tmorris,ou=people,dc=example,dc=com";
/* New value for userPassword                                        */
static char * npwd = "23skidoo";

/* Check for host, port, user and new password as arguments.         */
int get_user_args(int argc, char ** argv);

int
main(int argc, char ** argv)
{
    LDAP          * ld;                /* Handle to LDAP connection  */
    LDAPMod         modPW, * mods[2];  /* For modifying the password */
    char          * vals[2];           /* Value of modified password */
    struct berval   cred;              /* Client bind credentials    */
    struct berval * srvCred;           /* Server bind credentials    */
    int             ldapVersion;

    /* Use default hostname, server port, user, and new password
     * unless they are provided as arguments on the command line.    */
    if (get_user_args(argc, argv) != 0) return 1; /* Usage error     */
    
    /* Get a handle to an LDAP connection.                           */
    printf("Getting the handle to the LDAP connection...\n");
    if ((ld = ldap_init(host, port)) == NULL) {
        perror("ldap_init");
        return 1;
    }

    /* By default, the LDAP version is set to 2.                     */
    printf("Setting the version to LDAP v3...\n");
    ldapVersion = LDAP_VERSION3;
    ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion);

    /* Authenticate using the example SASL mechanism.                */
    printf("Bind DN is %s...\n", user);
    printf("Binding to the server using %s...\n", MECH);
    cred.bv_val = "magic";
    cred.bv_len = sizeof("magic") - 1;
    if (ldap_sasl_bind_s(ld, user, MECH, &cred, NULL, NULL, &srvCred)) {
        ldap_perror(ld, "ldap_sasl_bind_s");
        return 1;
    }

    /* Display the credentials returned by the server.               */
    printf("Server credentials: %s\n", srvCred->bv_val);

    /* Modify the user's password.                                   */
    printf("Modifying the password...\n");
    modPW.mod_op     = LDAP_MOD_REPLACE;
    modPW.mod_type   = "userpassword";
    vals[0]          = npwd;
    vals[1]          = NULL;
    modPW.mod_values = vals;

    mods[0] = &modPW; mods[1] = NULL;

    if (ldap_modify_ext_s(ld, user, mods, NULL, NULL)) {
        ldap_perror(ld, "ldap_modify_ext_s");
        return 1;
    }

    /* Finish up.                                                    */
    ldap_unbind(ld);
    printf("Modification was successful.\n");
    return 0;
}

Trying the SASL Client

The client changes Ted's password by binding to Directory Server, then requesting a modification to the userPassword attribute value.

After activating the plug-in in the server, compile the client code, saslclient.c. Next, run the client to perform the bind and the password modification.


$ ./saslclient 
Using the following connection info:
    host:    localhost
    port:    389
    bind DN: uid=tmorris,ou=people,dc=example,dc=com
    new pwd: 23skidoo
Getting the handle to the LDAP connection...
Setting the version to LDAP v3...
Bind DN is uid=tmorris,ou=people,dc=example,dc=com...
Binding to the server using my_sasl_mechanism...
Server credentials: my credentials
Modifying the password...
Modification was successful.
$ 

On the Directory Server side, the message showing that the plug-in has authenticated the client is in the errors log.


$ grep tmorris /local/ds/logs/errors | grep -i sasl 
[04/Jan/2006:12:05:30 +0100] - INFORMATION
 - testsasl_bind in test-saslbind plug-in
 - conn=12 op=0 msgId=1 - 
 Authenticated: uid=tmorris,ou=people,dc=example,dc=com