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