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

Chapter 6 Extending Client Request Handling Using Plug-Ins

This chapter covers support in the plug-in API for modifying what Directory Server does before or after carrying out operations for clients.

Although related to the bind operation, authentication is described independently in Chapter 7, Handling Authentication Using Plug-Ins.

This chapter cover the following topics:

Preoperation and Postoperation Plug-Ins

When processing client requests and when sending information to clients, Directory Server calls preoperation and postoperation plug-in functions.

Preoperation Plug-Ins

Preoperation plug-ins are called before a client request is processed. Use preoperation plug-ins to validate attribute values, and to add to and delete from attributes that are provided in a request, for example.

Postoperation Plug-Ins

Postoperation plug-ins are called after a client request has been processed and all results have been sent to the client, whether or not the operation completes successfully. Use postoperation plug-ins to send alerts, or to send alarms. Also use postoperation plug-ins to perform auditing work, or to perform cleanup work.


Note –

Directory Server calls preoperation and postoperation plug-ins only when an external client is involved. Internally, Directory Server also performs operations that no external client has requested.

To make this possible, Directory Server distinguishes between external operations and internal operations. External operations respond to incoming client requests. Internal operations are actions that are performed without a corresponding client request.


Several operations support the use of preoperation and postoperation plug-in functions. Operations include abandon, add, bind, compare, delete, modify, modify RDN, search, send entry, send referral, send result, and unbind. The plug-ins function regardless of the Directory Server front end contacted by the client application.

As is the case for other plug-in functions, slapi_pblock_set() is used in the plug-in initialization function to register preoperation and postoperation plug-in functions. slapi_pblock_set() is described in Part II, Directory Server Plug-In API Reference.

Registration Identifiers

Preoperation plug-in registrations use IDs of the form SLAPI_PLUGIN_PRE_operation_FN. Postoperation registrations use IDs of the form SLAPI_PLUGIN_POST_operation_FN. Here, operation is one of ABANDON, ADD, BIND, COMPARE, DELETE, ENTRY (sending entries to the client), MODIFY, MODRDN, REFERRAL (sending referrals to the client), RESULT (sending results to the client), SEARCH, or UNBIND.


Note –

Preoperation and postoperation plug-ins can also register functions to run at Directory Server startup, using SLAPI_PLUGIN_START_FN, and at Directory Server shutdown, using SLAPI_PLUGIN_STOP_FN.


Part II, Directory Server Plug-In API Reference describes these IDs. Refer also to install-path/include/slapi-plugin.h for the complete list of IDs used at build time.

The following example demonstrates how the functions are registered with Directory Server by using the appropriate registration IDs.


Example 6–1 Registering Postoperation Functions (testpostop.c)

#include "slapi-plugin.h"

Slapi_PluginDesc postop_desc = {
    "test-postop",                     /* plug-in identifier         */
    "Sun Microsystems, Inc.",          /* vendor name                */
    "6.0",                             /* plug-in revision number    */
    "Sample post-operation plug-in"    /* plug-in description        */
};

static Slapi_ComponentId * postop_id;  /* Used to set log            */

/* Register the plug-in with the server. */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
testpostop_init(Slapi_PBlock * pb)
{
    int rc = 0;                        /* 0 means success            */
    rc |= slapi_pblock_set(            /* Plug-in API version        */
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description        */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &postop_desc
    );
    rc |= slapi_pblock_set(            /* Open log at startup        */
        pb,
        SLAPI_PLUGIN_START_FN,
        (void *) testpostop_set_log
    );
    rc |= slapi_pblock_set(            /* Post-op add function       */
        pb,
        SLAPI_PLUGIN_POST_ADD_FN,
        (void *) testpostop_add
    );
    rc |= slapi_pblock_set(            /* Post-op modify function    */
        pb,
        SLAPI_PLUGIN_POST_MODIFY_FN,
        (void *) testpostop_mod
    );
    rc |= slapi_pblock_set(            /* Post-op delete function    */
        pb,
        SLAPI_PLUGIN_POST_DELETE_FN,
        (void *) testpostop_del
    );
    rc |= slapi_pblock_set(            /* Post-op modrdn function    */
        pb,
        SLAPI_PLUGIN_POST_MODRDN_FN,
        (void *) testpostop_modrdn
    );
    rc |= slapi_pblock_set(            /* Close log on shutdown      */
        pb,
        SLAPI_PLUGIN_CLOSE_FN,
        (void *) testpostop_free_log
    );
    /* Plug-in identifier is required for internal search.           */
    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &postop_id);
    return (rc);
}

In addition to its postoperation functions, the plug-in that is shown in the preceding example registers a function. The plug-in opens a log file at startup. The plug-in closes the log file at shutdown. For details, refer to install-path/examples/testpostop.c.

Location of Plug-In Examples

Plug-in examples are located in install-path/examples/.

Extending the Bind Operation

This section shows how to develop functions called by Directory Server before client bind operations.


Note –

Pre-bind plug-in functions are often used to handle extensions to authentication. Yet, you might have to account for special cases such as binds by the directory superuser and anonymous users. Sometimes, you have to account for multiple calls to the same preoperation or postoperation plug-in function.


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.

Logging the Authentication Method

The following example logs the bind authentication method. Refer to install-path/examples/testpreop.c for complete example code.


Example 6–2 Logging the Authentication Method (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_bind(Slapi_PBlock * pb)
{
    char * auth;                       /* Authentication type     */
    char * dn;                         /* Target DN               */
    int    method;                     /* Authentication method   */
    int    connId, opId, rc = 0;
    long   msgId;

    /* Get target DN for bind and authentication method used.     */
    rc |= slapi_pblock_get(pb, SLAPI_BIND_TARGET,     &dn);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_METHOD,     &method);
    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) {
        switch (method) {
            case LDAP_AUTH_NONE:   auth = "No authentication";
                break;
            case LDAP_AUTH_SIMPLE: auth = "Simple authentication";
                break;
            case LDAP_AUTH_SASL:   auth = "SASL authentication";
                break;
            default: auth = "Unknown authentication method";
                break;
        }
    } else {
        return (rc);
    }

    /* Log target DN and authentication method info.              */
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpreop_bind in test-preop plug-in",
        "Target DN: %s\tAuthentication method: %s\n", dn, auth
    );
    return (rc);
}

This plug-in function sets the auth message based on the authentication method. The function does nothing to affect the way Directory Server processes the bind.

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 Generate a Bind Log Message

  1. Bind as Kirsten Vaughan (for example).


    $ ldapsearch -h localhost -p 1389 -b "dc=example,dc=com" \
     -D "uid=kvaughan,ou=people,dc=example,dc=com" -w bribery "(uid=*)"
  2. Search instance-path/logs/errors for the resulting message from the testpreop_bind() function.

    If you ignore housekeeping information for the entry, output similar to this appears:


    Target DN: uid=kvaughan,ou=people,dc=example,dc=com
    Authentication method: Simple authentication

    For a discussion of less trivial pre-bind plug-in functions, refer to Chapter 7, Handling Authentication Using Plug-Ins.

Bypassing Bind Processing in Directory Server

When the plug-in returns 0, Directory Server continues to process the bind. To bypass Directory Server bind processing, set SLAPI_CONN_DN in the parameter block, and return a positive value, such as 1.

Normal Directory Server Bind Behavior

Directory Server follows the LDAP bind model. At minimum, the server authenticates the client. The server also sends a bind response to indicate the status of authentication. Refer to RFC 1777, Lightweight Directory Access Protocol, and RFC 45111, Lightweight Directory Access Protocol (v3), for details.


Note –

Lightweight Directory Access Protocol (v3) is the preferred protocol because Lightweight Directory Access Protocol (v2) is obsolete.


Extending the Search Operation

This section shows how to develop functionality called by Directory Server before LDAP search operations.

Logging Who Requests a Search

The following example logs the DN of the client that requests the search. Refer to install-path/examples/testbind.c for complete example code.

Before using the plug-in function as shown in this section, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously. The plug-in, Test Bind, also includes the pre-search function.

The test_search() function logs the request, as shown in the following example.


Example 6–3 Getting the DN of the Client Requesting a Search (testbind.c)

#include "slapi-plugin.h"

int
test_search(Slapi_PBlock * pb)
{
    char * requestor_dn;               /* DN of client searching    */
    int    is_repl;                    /* Is this replication?      */
    int    is_intl;                    /* Is this an internal op?   */
    int    connId, opId, rc = 0;
    long   msgId;

    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_REQUESTOR_DN,            &requestor_dn);
    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) return (rc);

    /* Do not interfere with replication and internal operations.   */
    if (is_repl || is_intl) return 0;

    if (requestor_dn != NULL && *requestor_dn != '\0') {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "test_search in test-bind plug-in",
            "Search requested by %s\n", requestor_dn
        );
    } else {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "test_search in test-bind plug-in",
            "Search requested by anonymous client.\n"
        );
    }
    return (rc);
}

After activating the plug-in in the server, perform a search.


$ ldapsearch -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 -h localhost -p 1389 -b dc=example,dc=com uid=bjensen

Search instance-path/logs/errors for the resulting message. The last field of the log entry shows the following:

With the plug-in activated in Directory Server, perform a search as Kirsten Vaughan:

Authenticated: uid=kvaughan,ou=people,dc=example,dc=com

Breaking Down a Search Filter

Breaking Down a Search Filter breaks down a search filter, logging the component parts of the filter. For complete example code, refer to install-path/examples/testpreop.c.

LOG Macros for Compact Code

The code for the SLAPI_PLUGIN_PRE_SEARCH_FN function, testpreop_search(), is preceded by three macros. Search testpreop.c for #define LOG1(format). The purpose of these macros is only to render the subsequent logging statements more compact, making the code easier to decipher. The digits in LOG1, LOG2, and LOG3 reflect the number of parameters each macro takes. The actual number of parameters that you pass to the log functions varies, as the log functions let you format strings in the style of printf().

Parameter Block Contents

The parameter block passed to the pre-search function contains information about the target and scope of the search. The following example shows how to obtain this information. The scope, as specified in RFC 4511, Lightweight Directory Access Protocol (v3), can include one of the following:


Example 6–4 Logging Base and Scope for a Search (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_search(Slapi_PBlock * pb)
{
    char          *  base       = NULL;/* Base DN for search      */
    int              scope;            /* Base, 1 level, subtree  */
    int              rc = 0;

    rc |= slapi_pblock_get(pb, SLAPI_SEARCH_TARGET,    &base);
    rc |= slapi_pblock_get(pb, SLAPI_SEARCH_SCOPE,     &scope);
    if (rc == 0) {
        switch (scope) {
        case LDAP_SCOPE_BASE:
            LOG2("Target DN: %s\tScope: LDAP_SCOPE_BASE\n", base);
            break;
        case LDAP_SCOPE_ONELEVEL:
            LOG2("Target DN: %s\tScope: LDAP_SCOPE_ONELEVEL\n", base);
            break;
        case LDAP_SCOPE_SUBTREE:
            LOG2("Target DN: %s\tScope: LDAP_SCOPE_SUBTREE\n", base);
            break;
        default:
            LOG3("Target DN: %s\tScope: unknown value %d\n", base, scope);
            break;
        }
    } /* Continue processing... */

    return 0;
}

In writing a plug-in mapping X.500 search targets to LDAP search targets, you could choose to parse and transform the base DN for searches.

The parameter block also contains the following information:

Search Filter Info

The search filter that you obtain from the parameter block must be considered. testpreop_search() gets the filter from the parameter block both as a Slapi_Filter structure, filter, and as a string, filter_str. Depending on what kind of search filter is passed to Directory Server and retrieved in the Slapi_Filter structure, the plug-in breaks the filter down in different ways. If the function were post-search rather than pre-search, you would likely want to access the results that are returned through the parameter block. See Part II, Directory Server Plug-In API Reference for instructions on accessing the results.

The plug-in API offers several functions for manipulating the search filter. testpreop_search() uses slapi_filter_get_choice() to determine the kind of filter used. The preoperation search function also uses slapi_filter_get_subfilt() to break down substrings that are used in the filter. The preoperation search function further uses slapi_filter_get_type() to find the attribute type when searching for the presence of an attribute. The preoperation search function uses slapi_filter_get_ava() to obtain attribute types and values used for comparisons.

The following example shows a code excerpt that retrieves information from the search filter.


Example 6–5 Retrieving Filter Information (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_search(Slapi_PBlock * pb)
{
    char          *  attr_type  = NULL;/* For substr and compare  */
    char          *  substr_init= NULL;/* For substring filters   */
    char          *  substr_final=NULL;
    char          ** substr_any = NULL;
    int              i, rc =0;
    Slapi_Filter  *  filter;

    rc |= slapi_pblock_get(pb, SLAPI_SEARCH_FILTER,    &filter);
    if (rc == 0) {
        filter_type = slapi_filter_get_choice(filter);
        switch (filter_type) {
        case LDAP_FILTER_AND:
        case LDAP_FILTER_OR:
        case LDAP_FILTER_NOT:
            LOG1("Search filter: complex boolean\n");
            break;
        case LDAP_FILTER_EQUALITY:
            LOG1("Search filter: LDAP_FILTER_EQUALITY\n");
            break;
        case LDAP_FILTER_GE:
            LOG1("Search filter: LDAP_FILTER_GE\n");
            break;
        case LDAP_FILTER_LE:
            LOG1("Search filter: LDAP_FILTER_LE\n");
            break;
        case LDAP_FILTER_APPROX:
            LOG1("Search filter: LDAP_FILTER_APPROX\n");
            break;
        case LDAP_FILTER_SUBSTRINGS:
            LOG1("Search filter: LDAP_FILTER_SUBSTRINGS\n");
            slapi_filter_get_subfilt(
                filter,
                &attr_type,
                &substr_init,
                &substr_any,
                &substr_final
            );
            if (attr_type != NULL)
                LOG2("\tAttribute type: %s\n", attr_type);
            if (substr_init != NULL)
                LOG2("\tInitial substring: %s\n", substr_init);
            if (substr_any != NULL)
                for (i = 0; substr_any[i] != NULL; ++i) {
                    LOG3("\tSubstring# %d: %s\n", i, substr_any[i]);
                }
            if (substr_final != NULL)
                LOG2("\tFinal substring: %s\n", substr_final);
            break;
        case LDAP_FILTER_PRESENT:
            LOG1("Search filter: LDAP_FILTER_PRESENT\n");
            slapi_filter_get_attribute_type(filter, &attr_type);
            LOG2("\tAttribute type: %s\n", attr_type);
            break;
        case LDAP_FILTER_EXTENDED:
            LOG1("Search filter: LDAP_FILTER_EXTENDED\n");
            break;
        default:
            LOG2("Search filter: unknown type %d\n", filter_type);
            break;
        }
    }
    return (rc);
}

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously.

After the example suffix and plug-in have been loaded into the directory, run a search to generate output in instance-path/logs/errors.


$ ldapsearch -h localhost -p 1389 -b "dc=example,dc=com" "(uid=*go*iv*)"

When you ignore the housekeeping information in the error log, the search filter breakdown occurss as shown in the following example.


Example 6–6 Search Filter Breakdown

*** PREOPERATION SEARCH PLUG-IN - START ***
Target DN: dc=example,dc=com       Scope: LDAP_SCOPE_SUBTREE
Dereference setting: LDAP_DEREF_NEVER
Search filter: LDAP_FILTER_SUBSTRINGS
        Attribute type: uid
        Substring# 0: go
        Substring# 1: iv
String representation of search filter: (uid=*go*iv*)
*** PREOPERATION SEARCH PLUG-IN - END ***

Building a Filter Info

When building a filter, notice that the plug-in API provides slapi_str2filter() to convert strings to Slapi_Filter data types, and slapi_filter_join() to join simple filters with boolean operators AND, OR, or NOT. Free the filter with slapi_filter_free(). Refer to Part II, Directory Server Plug-In API Reference for details.

Normal Directory Server Search Behavior

Directory Server gets a list of candidate entries, then iterates through the list to check which entries match the search criteria. The plug-in then puts the set of results in the parameter block. In most cases, searching within a plug-in is typically performed with slapi_search_internal*() calls.

Extending the Compare Operation

This section shows how to develop functionality called by Directory Server before a client compare operation. The following example logs the target DN and attribute of the entry with which to compare values. For complete example code, refer to install-path/examples/testpreop.c.


Example 6–7 Plug-In Comparison Function (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_cmp(Slapi_PBlock * pb)
{
    char * dn;                         /* Target DN               */
    char * attr_type;                  /* Type of attr to compare */
    /* Attribute value could be lots of things, even a binary file.
     * Here, do not try to retrieve the value to compare.         */
    int    connId, opId, rc = 0;
    long   msgId;

    rc |= slapi_pblock_get(pb, SLAPI_COMPARE_TARGET,  &dn);
    rc |= slapi_pblock_get(pb, SLAPI_COMPARE_TYPE,    &attr_type);
    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) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_cmp in test-preop plug-in",
            "Target DN: %s\tAttribute type: %s\n", dn, attr_type
        );
    }

    return (rc);
}

The plug-in function can access the attribute value to use for the comparison as well. The attribute value in the parameter block is in a berval structure. Thus, the value could be binary data such as a JPEG image. No attempt is made to write the value to the logs.

The following example shows the slapi_pblock_get() call used to obtain the attribute value.


Example 6–8 Obtaining the Attribute Value

#include "slapi-plugin.h"

int
my_compare_fn(Slapi_PBlock * pb)
{
    int             rc = 0;
    struct berval * attr_val;

    /* Obtain the attribute value from the parameter block */
    rc |= slapi_pblock_get(pb, SLAPI_COMPARE_VALUE, &attr_val);

    if (rc != 0) {
        rc = LDAP_OPERATIONS_ERROR;
        slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
        return 0;
    }

    /* Do something with the value here.                   */

    return 0;
}

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously.

Try the example plug-in function using the ldapcompare tool installed with the Directory Server Resource Kit.


Example 6–9 Performing a Comparison


$ ldapcompare sn:Jensen uid=bjensen,ou=people,dc=example,dc=com
comparing type: "sn" value: "Jensen" in entry
 "uid=bjensen,ou=people,dc=example,dc=com"
compare TRUE

The log entry in instance-path/logs/errors shows the following results, not including housekeeping information at the beginning of the log entry:

Target DN: uid=bjensen,ou=people,dc=example,dc=com	Attribute type: sn

Extending the Add Operation

This section shows how to develop functionality called by Directory Server before and after a client add operation.

Prepending a String to an Attribute

Example 39–10 prepends the string ADD to the description attribute of the entry to be added to the directory. For complete example code, refer to instance-path/examples/testpreop.c.

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously.

To try the preoperation add function, add an entry for Quentin Cubbins who recently joined Example.com. Quentin’s LDIF entry, quentin.ldif, reads as shown in the following example.


Example 6–10 LDIF Entry


dn: uid=qcubbins,ou=People,dc=example,dc=com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
userPassword: qcubbins
secretary: uid=bjensen,ou=People,dc=example,dc=com
description: Entry for Quentin Cubbins

The following example performs the prepend.


Example 6–11 Prepending ADD to the Description of the Entry (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_add(Slapi_PBlock * pb)
{
    Slapi_Entry * entry;               /* Entry to add            */
    Slapi_Attr  * attribute;           /* Entry attributes        */
    Slapi_Value ** values;             /* Modified, duplicate vals*/
    int         connId, opId, rc = 0;
    long        msgId;

    /* Get the entry. */
    rc |= slapi_pblock_get(pb, SLAPI_ADD_ENTRY,       &entry);
    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);

    /* Prepend ADD to values of description attribute.            */
    rc |= slapi_entry_attr_find(entry, "description", &attribute);
    if (rc == 0) {                 /* Found a description, so...  */
        int nvals, i, count = 0;
        const Slapi_Value * value;

        slapi_attr_get_numvalues(attribute, &nvals);
        values = (Slapi_Value **)
            slapi_ch_malloc((nvals+1)*sizeof(Slapi_Value *));

        for (                          /* ...loop for value...    */
            i = slapi_attr_first_value_const(attribute, &value);
            i != -1;
            i = slapi_attr_next_value_const(attribute, i, &value)
        ) {                            /* ...prepend "ADD ".      */
            const char  * string;
            char        * tmp;
            values[count] = slapi_value_dup(value);
            string = slapi_value_get_string(values[count]);
            tmp    = slapi_ch_malloc(5+strlen(string));
            strcpy(tmp, "ADD ");
            strcpy(tmp+4, string);
            slapi_value_set_string(values[count], tmp);
            slapi_ch_free((void **)&tmp);
            ++count;
        }

        values[count] = NULL;

    } else {                           /* entry has no desc.      */
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_add in test-preop plug-in",
            "Entry has no description attribute.\n"
        );
    }

    rc = slapi_entry_attr_replace_sv(entry, "description", values);
    if (rc != 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_add in test-preop plug-in",
            "Description attribute(s) not modified.\n"
        );
    }
    slapi_valuearray_free(&values);

    return 0;
}

Add Quentin’s entry to the directory. For example, if the entry is in quentin.ldif, type:


$ ldapmodify -h localhost -p 1389 -a -f quentin.ldif \
 -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery
adding new entry uid=qcubbins,ou=People,dc=example,dc=com

$ 

At this point, search the directory for Quentin’s entry.


Example 6–12 Searching for the Entry


$ ldapsearch -h localhost -p 1389 -b dc=example,dc=com uid=qcubbins
version: 1
dn: uid=qcubbins,ou=People,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
secretary: uid=bjensen,ou=People,dc=example,dc=com
description: ADD Entry for Quentin Cubbins
$ 

Notice the value of the description attribute.

Delete Quentin’s entry so you can use it again later as an example.


$ ldapdelete -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 uid=qcubbins,ou=people,dc=example,dc=com

Turn off the preoperation plug-in to avoid prepending ADD to all the entries that you add.


$ dsconf disable-plugin -h localhost -p 1389 "Test Preop"
$ dsadm restart instance-path

Logging the Entry to Add

Logging the Entry to Add logs the entry to add. For complete example code, refer to instance-path/examples/testpostop.c.

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation.

The testpostop_add() function logs the DN of the added entry and also writes the entry to a log created by the plug-in, changelog.txt. The location of changelog.txt depends on the platform, as shown in the following example. The changelog.txt file managed by the plug-in is not related to other change logs managed by Directory Server.


Example 6–13 Tracking Added Entries (testpostop.c)

#include "slapi-plugin.h"

int
testpostop_add(Slapi_PBlock * pb)
{
    char        * dn;                  /* DN of entry to add         */
    Slapi_Entry * entry;               /* Entry to add               */
    int           is_repl = 0;         /* Is this replication?       */
    int           connId, opId, rc = 0;
    long          msgId;

    rc |= slapi_pblock_get(pb, SLAPI_ADD_TARGET,              &dn);
    rc |= slapi_pblock_get(pb, SLAPI_ADD_ENTRY,               &entry);
    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);

    if (rc != 0) return (rc);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpostop_add in test-postop plug-in",
        "Added entry (%s)\n", dn
    );

    /* In general, do not interfere in replication operations.       */
    /* Log the DN and the entry to the change log file.              */
    if (!is_repl) write_changelog(_ADD, dn, (void *) entry, 0);

    return (rc);
}

After activating the plug-in, add Quentin’s entry:


$ ldapmodify -a -D uid=kvaughan,ou=people,dc=example,dc=com \
 -h localhost -p 1389 -w bribery -f quentin.ldif
adding new entry uid=qcubbins,ou=People,dc=example,dc=com

$ 

Search instance-path/logs/errors for the log message. If you ignore housekeeping information, you get the following message:

Added entry (uid=qcubbins,ou=people,dc=example,dc=com)

Notice also the entry logged to changelog.txt. The entry resembles the LDIF that was added, except that the change log has time stamps attached for internal use as shown in the following example.


Example 6–14 Example changelog.txt Entry

time: 21120506172445
dn: uid=qcubbins,ou=people,dc=example,dc=com
changetype: add
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
secretary: uid=bjensen,ou=People,dc=example,dc=com
userPassword: qcubbins
creatorsName: cn=Directory Manager
modifiersName: cn=Directory Manager
createTimestamp: 21120506152444Z
modifyTimestamp: 21120506152444Z

Extending the Modify Operation

This section shows how to develop functionality called by Directory Server after a client modify operation. A preoperation plug-in, not demonstrated here, can be found in install-path/examples/testpreop.c. Refer to install-path/examples/testpostop.c for the source code discussed here.

Before using the plug-in function as described here, set up Directory Server as described in Logging the Entry to Add, making sure to add Quentin’s entry.

The testpostop_mod() function logs the DN of the modified entry and also writes the entry to a log managed by the plug-in, changelog.txt. The location of changelog.txt depends on the platform, as shown in the source code.

The following example shows the code that performs the logging.


Example 6–15 Tracking Modified Entries (testpostop.c)

#include "slapi-plugin.h"

int
testpostop_mod(Slapi_PBlock * pb)
{
    char    *  dn;                     /* DN of entry to modify      */
    LDAPMod ** mods;                   /* Modifications to apply     */
    int        is_repl = 0;            /* Is this replication?       */
    int        connId, opId, rc = 0;
    long       msgId;

    rc |= slapi_pblock_get(pb, SLAPI_MODIFY_TARGET,           &dn);
    rc |= slapi_pblock_get(pb, SLAPI_MODIFY_MODS,             &mods);
    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);
    
    if (rc != 0) return (rc);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpostop_mod in test-postop plug-in",
        "Modified entry (%s)\n", dn
    );

    /* In general, do not interfere in replication operations.       */
    /* Log the DN and the modifications made to the change log file. */
    if (!is_repl) write_changelog(_MOD, dn, (void *) mods, 0);

    return (rc);
}

First, check that Quentin’s entry is in the directory.


Example 6–16 Checking the Directory for an Entry


$ ldapsearch -h localhost -p 1389 -b dc=example,dc=com uid=qcubbins
version: 1
dn: uid=qcubbins,ou=People,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
uid: qcubbins
givenName: Quentin
sn: Cubbins
cn: Quentin Cubbins
mail: qcubbins@example.com
secretary: uid=bjensen,ou=People,dc=example,dc=com

After the plug-in is activated in Directory Server, modify Quentin’s mail address.


Example 6–17 Modifying a Mail Address


$ ldapmodify -h localhost -p 1389 \
 -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery
dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modify
replace: mail
mail: quentin@example.com
^D

Search instance-path/logs/errors for the log message. If you ignore housekeeping information, you get the following message:

Modified entry (uid=qcubbins,ou=people,dc=example,dc=com)

Notice also the information logged to changelog.txt as shown in the following example.


Example 6–18 Example changelog.txt After Modification

time: 21120506181305
dn: uid=qcubbins,ou=people,dc=example,dc=com
changetype: modify
replace: mail
mail: quentin@example.com
-
replace: modifiersname
modifiersname: cn=Directory Manager
-
replace: modifytimestamp
modifytimestamp: 21120506161305Z
-

Extending the Rename Operation

This section demonstrates functionality called by Directory Server after a client modify RDN operation. A preoperation plug-in, not demonstrated here, can be found in install-path/examples/testpreop.c. Refer to install-path/examples/testpostop.c for the source code discussed here.

Before using the plug-in function as described here, set up Directory Server as described in Logging the Entry to Add, making sure to add Quentin’s entry.

The testpostop_modrdn() function logs the DN of the modified entry and also writes the entry to a log managed by the plug-in, changelog.txt. The location of changelog.txt depends on the platform, as shown in the source code.

The following example shows the code that performs the logging.


Example 6–19 Tracking Renamed Entries (testpostop.c)

#include "slapi-plugin.h"

int
testpostop_modrdn( Slapi_PBlock *pb )
{
    char * dn;                         /* DN of entry to rename      */
    char * newrdn;                     /* New RDN                    */
    int    dflag;                      /* Delete the old RDN?        */
    int    is_repl = 0;                /* Is this replication?       */
    int    connId, opId, rc = 0;
    long   msgId;

    rc |= slapi_pblock_get(pb, SLAPI_MODRDN_TARGET,           &dn);
    rc |= slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN,           &newrdn);
    rc |= slapi_pblock_get(pb, SLAPI_MODRDN_DELOLDRDN,        &dflag);
    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);

    if (rc != 0) return (rc);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpostop_modrdn in test-postop plug-in",
        "Modrdn entry (%s)\n", dn
    );

    /* In general, do not interfere in replication operations.       */
    /* Log the DN of the renamed entry, its new RDN, and the flag
     * indicating whether the old RDN was removed to the change log. */
    if (!is_repl) write_changelog(_MODRDN, dn, (void *) newrdn, dflag);

    return (rc);
}

Check that Quentin’s entry is in the directory as shown in Extending the Modify Operation.

Last weekend, Quentin decided to change his given name to Fred. His user ID is now fcubbins, so his entry must be renamed. With this plug-in activated in Directory Server, modify the entry.


Example 6–20 Renaming an Entry


$ ldapmodify -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 -h localhost -p 1389
dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modify
replace: givenName
givenName: Fred

dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modify
replace: mail
mail: fcubbins@example.com

dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modify
replace: cn
cn: Fred Cubbins

dn: uid=qcubbins,ou=People,dc=example,dc=com
changetype: modrdn
newrdn: uid=fcubbins
deleteoldrdn: 1
^D

Search instance-path/logs/errors for the log message. If you ignore housekeeping information, you get the following message:

Modrdn entry (uid=qcubbins,ou=people,dc=example,dc=com)

Notice also the information that is logged to changelog.txt.


Example 6–21 Example changelog.txt Entry After Rename


time: 21120506184432
dn: uid=qcubbins,ou=people,dc=example,dc=com
changetype: modrdn
newrdn: uid=fcubbins
deleteoldrdn: 1

Extending the Delete Operation

This section shows how to develop functionality called by Directory Server after a client delete operation. A preoperation plug-in, not demonstrated here, can be found in install-path/examples/testpreop.c. Refer to install-path/examples/testpostop.c for the source code discussed here.

Before using the plug-in function as described here, set up Directory Server as described in Logging the Entry to Add, making sure to add Quentin’s entry.

The testpostop_modrdn() function logs the DN of the modified entry and also writes the entry to a log managed by the plug-in, changelog.txt. The location of changelog.txt depends on the platform, as shown in the source code.

The following example shows the code that performs the logging.


Example 6–22 Tracking Entry Deletion (testpostop.c)

#include "slapi-plugin.h"

int
testpostop_del( Slapi_PBlock *pb )
{
    char * dn;                         /* DN of entry to delete      */
    int    is_repl = 0;                /* Is this replication?       */
    int    connId, opId, rc = 0;
    long   msgId;

    rc |= slapi_pblock_get(pb, SLAPI_DELETE_TARGET,           &dn);
    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);
    
    if (rc != 0) return (rc);
    slapi_log_info_ex(
        SLAPI_LOG_INFO_AREA_PLUGIN,
        SLAPI_LOG_INFO_LEVEL_DEFAULT,
        msgId,
        connId,
        opId,
        "testpostop_del in test-postop plug-in",
        "Deleted entry (%s)\n", dn
    );

    /* In general, do not interfere in replication operations.       */
    /* Log the DN of the deleted entry to the change log.            */
    if (!is_repl) write_changelog(_DEL, dn, NULL, 0);

    return (rc);
}

First, check that Quentin’s entry is in the directory as shown in Extending the Modify Operation.

Quentin’s name might be Fred if you have renamed the entry as described in Extending the Rename Operation.

Imagine that Quentin shouted copious verbal abuse at a key customer causing Quentin to be fired from Example.com. With this plug-in activated in Directory Server, delete his entry.

$ ldapdelete -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 uid=qcubbins,ou=People,dc=example,dc=com

Search instance-path/logs/errors for the log message. If you ignore housekeeping information, you get the following message:

Deleted entry (uid=qcubbins,ou=people,dc=example,dc=com)

Notice also the information logged to changelog.txt as shown in the following example.


Example 6–23 Example changelog.txt After Deletion

time: 21120506185404
dn: uid=qcubbins,ou=people,dc=example,dc=com
changetype: delete

Intercepting Information Sent to the Client

This section demonstrates functionality called by Directory Server before sending a result code, a referral, or an entry to the client application. Refer to install-path/examples/testpreop.c for the source code discussed here.

The following example shows the code that logs the operation number and user DN.


Example 6–24 Logging and Responding to the Client (testpreop.c)

#include "slapi-plugin.h"

int
testpreop_send(Slapi_PBlock * pb)
{
    Slapi_Operation * op;              /* Operation in progress   */
    char            * connDn;          /* Get DN from connection  */
    int               connId, opId, rc = 0;
    long              msgId;

    rc |= slapi_pblock_get(pb, SLAPI_OPERATION,       &op);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_DN,         &connDn);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,         &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID, &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,    &opId);
    if (rc == 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_send in test-preop plug-in",
            "Operation: %d\tUser: %s\n", slapi_op_get_type(op), connDn
        );
    }
    slapi_ch_free_string(&connDn);

    return (rc);
}

Operation identifiers are defined in the plug-in header file install-path/include/slapi-plugin.h. Search for SLAPI_OPERATION_*.

Before using the plug-in function as described here, set up the example suffix and register the plug-in. See Extending the Bind Operation and “To register the Plug-in”, as described previously.

With the plug-in activated in Directory Server, perform a search as Kirsten Vaughan:


$ ldapsearch -h localhost -p 1389 -b dc=example,dc=com \
 -D uid=kvaughan,ou=people,dc=example,dc=com -w bribery \
 uid=bcubbins

Search instance-path/logs/errors for the log messages. Minus housekeeping information, the first message reflects the bind result:

Operation: 1 User: uid=kvaughan,ou=people,dc=example,dc=com

The next message reflects the search:

Operation: 4 User: uid=kvaughan,ou=people,dc=example,dc=com

Inside plug-in functions, use slapi_op_get_type() to determine the type of an operation. Refer to Part II, Directory Server Plug-In API Reference for info about slapi_op_get_type(), or see the plug-in header file install-path/include/slapi-plugin.h for a list of operation types.