Sun Java logo     Previous      Contents      Index      Next     

Sun logo
Sun Java(TM) System Directory Server 5.2 2005Q1 Plug-In Developer's Guide 

Chapter 5
Extending Client Request Handling

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 describe independently in Chapter 6, "Handling Authentication."


Pre-Operation and Post-Operation Plug-Ins

Before and after performing processing client requests and before and after sending information to clients, Directory Server calls pre-operation and post-operation plug-in functions, respectively.

Pre-Operation Plug-Ins

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

Post-Operation Plug-Ins

Post-operation plug-ins are called after a client request has been processed and all results sent to the client, whether or not the operation completes successfully. Use post-operation plug-ins to send alerts and alarms, and to perform auditing and clean up work, for example.


Note

Directory Server calls pre- and post-operation plug-ins only when an external client is involved. (Internally, Directory Server also performs operations 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 those performed without a corresponding client request.


Operations supporting the use of pre- and post-operation plug-in functions include abandon, add, bind, compare, delete, modify, modify RDN, search, send entry, send referral, send result, and unbind, regardless of 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 pre- and post-operation plug-in functions. slapi_pblock_set() is described in the Directory Server Plug-in Developer's Reference.

Registration Identifiers

Pre- and post-operation plug-in registrations use IDs of the form SLAPI_PLUGIN_PRE_operation_FN or SLAPI_PLUGIN_POST_operation_FN, where 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

Pre- and post-operation plug-ins may also register functions to run at Directory Server startup, using SLAPI_PLUGIN_START_FN, and at Directory Server shutdown, using SLAPI_PLUGIN_STOP_FN.


The Directory Server Plug-in Developer's Reference describes these IDs. Refer also to ServerRoot/plugins/slapd/slapi/include/slapi-plugin.h for the complete list of IDs used at build time.

Code Example 5-1 demonstrates how the functions are registered with Directory Server using the appropriate registration IDs.

Code Example 5-1 Registering Post-Operation Functions (testpostop.c

#include "slapi-plugin.h"

Slapi_PluginDesc postop_desc = {

    "test-postop",                     /* plug-in identifier         */

    "Sun Microsystems, Inc.",          /* vendor name                */

    "5.2",                             /* 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 post-operation functions, the plug-in shown in Code Example 5-1 registers a function to open a log file at startup and close the log file at shutdown. For details, refer to ServerRoot/plugins/slapd/slapi/examples/testpostop.c.

Finding Examples

Find plug-in examples under ServerRoot/plugins/slapd/slapi/examples/.


Extending the Bind Operation

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


Tip

Pre-bind plug-in functions are often used to handle extensions to authentication, but you may need to account for special cases such as binds by the Directory Manager and anonymous users, and sometimes multiple calls to the same pre- or post-operation plug-in function.

For details, refer to Chapter 6, "Handling Authentication."


Setting Up an Example Suffix

If you have not done so already, prepare some example data by creating a directory suffix, dc=example,dc=com, whose users we load from an LDIF file, ServerRoot/slapd-serverID/ldif/Example-Plugin.ldif. You may do this using either the console, or client tools on the command line. The following steps use the command line for example:

  1. Prepare an LDIF file specifying the backend and suffix for Example.com.

    Code Example 5-2 LDIF File for an Example.com Suffix 

    dn: cn="dc=example,dc=com",cn=mapping tree,cn=config

    objectclass: top

    objectclass: extensibleObject

    objectclass: nsMappingTree

    nsslapd-state: backend

    nsslapd-backend: example

    cn: dc=example,dc=com

    dn: cn=example,cn=ldbm database, cn=plugins, cn=config

    objectClass: top

    objectClass: extensibleObject

    objectClass: nsBackendInstance

    nsslapd-suffix: dc=example,dc=com

  2. Add the suffix to the Directory Server instance.
  3. $ ldapmodify -a -p port -h host -D "cn=Directory Manager" -w passwd -f file

    Here, file is the LDIF file from Code Example 5-2.

  4. Stop Directory Server.
  5. $ ServerRoot/slapd-serverID/stop-slapd

  6. Load ServerRoot/slapd-serverID/ldif/Example-Plugin.ldif.
  7. $ ServerRoot/slapd-serverID/ldif2db -s "dc=example,dc=com" \
    -i ServerRoot/slapd-serverID/ldif/Example-Plugin.ldif

  8. Start Directory Server.
  9. $ ServerRoot/slapd-serverID/start-slapd

Logging the Authentication Method

Code Example 5-3 logs the bind authentication method. Refer to ServerRoot/plugins/slapd/slapi/examples/testpreop.c for complete example code.

Code Example 5-3 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. It does nothing to affect the way Directory Server processes the bind.

Registering 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 Test Preop plug-in.

  1. Build the plug-in.
  2. Hint Use ServerRoot/plugins/slapd/slapi/examples/Makefile.

  3. Load the plug-in configuration entry.
  4. Hint Start with the configuration entry specified in the introductory comments of ServerRoot/plugins/slapd/slapi/examples/testpreop.c.

    Code Example 5-4 Configuration Entry (testpreop.c

    dn: cn=Test Preop,cn=plugins,cn=config

    objectClass: top

    objectClass: nsSlapdPlugin

    objectClass: extensibleObject

    cn: Test Preop

    nsslapd-pluginPath: <ServerRoot>/plugins/slapd/slapi/examples/<LibName>

    nsslapd-pluginInitfunc: testpreop_init

    nsslapd-pluginType: preoperation

    nsslapd-pluginEnabled: on

    nsslapd-plugin-depends-on-type: database

    nsslapd-pluginId: test-preop

    nsslapd-pluginVersion: 5.2

    nsslapd-pluginVendor: Sun Microsystems, Inc.

    nsslapd-pluginDescription: Sample pre-operation plug-in

  5. Restart Directory Server.
  6. Turn on informational logging for plug-ins.
  7. Refer to Set Appropriate Log Level in the Directory Server Configuration for details.

Generating a Bind Log Message

If you use the console, it generates messages whenever the admin user binds. For something different:

  1. Bind as Plinner Blinn.
  2. $ ldapsearch -p port -h host -b "dc=example,dc=com" \
    -D "uid=pblinn,ou=People,dc=example,dc=com" -w pblinn "(uid=*)"

  3. Search ServerRoot/slapd-serverID/logs/errors for the resulting message from the testpreop_bind() function.
  4. Ignoring housekeeping information for the entry, you find something like this:

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

For a discussion of less trivial pre-bind plug-in functions, refer to Chapter 6, "Handling Authentication."

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 Behavior

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


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 first example discussed here logs the DN of the client requesting the search. Refer to ServerRoot/plugins/slapd/slapi/examples/testbind.c for complete example code.

Before using the plug-in function as described here, set up the example suffix and register the plug-in as described under Setting Up an Example Suffix and Registering the Plug-In, loading the configuration entry from the introductory comments of testbind.c. (The plug-in named Test Bind also includes our pre-search function.) Code Example 5-4 shows a similar configuration entry.

Adjust nsslapd-pluginPath to fit how the product is installed on your system. Notice the value of nsslapd-pluginType: preoperation.

The test_search() function logs the request, as shown in Code Example 5-5.

Code Example 5-5 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);

}

With the plug-in active in the server, perform a search.

$ ldapsearch -D "uid=hfuddnud,ou=People,dc=example,dc=com" \
-w hfuddnud -p port -h host -b "dc=example,dc=com" "(uid=pblinn)"

Search ServerRoot/slapd-serverID/logs/errors for the resulting message. The last field of the log entry shows:

Search requested by uid=hfuddnud,ou=people,dc=example,dc=com

Breaking Down a Search Filter

The second example discussed here breaks down a search filter, logging the component parts of the filter. For complete example code, refer to ServerRoot/plugins/slapd/slapi/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 passed 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. Code Example 5-6 shows how to obtain this information. The scope, as specified in RFC 2251, Lightweight Directory Access Protocol (v3), can be the base object, which is the base DN itself, a single level below the base DN, or the whole subtree below the base DN.

Code Example 5-6 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, for example, you could choose to parse and transform the base DN for searches.

The parameter block also contains information about when aliases are resolved during the search, how much Directory Server time and resources the search is set to consume, and which attribute types are to be returned.

Peeking Into the Search Filter

Of particular interest here is the search filter you obtain from the parameter block. 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 returned through the parameter block. Refer to the Directory Server Plug-in Developer's Reference for details on how to access 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, slapi_filter_get_subfilt() to break down substrings used in the filter, slapi_filter_get_type() to find the attribute type when searching for the presence of an attribute, and slapi_filter_get_ava() to obtain attribute types and values used for comparisons.

Code Example 5-7 shows a code excerpt that retrieves some information from the search filter.

Code Example 5-7 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 as described under Setting Up an Example Suffix and Registering the Plug-In.

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

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

Ignoring the housekeeping information in the error log, the search filter breakdown happens as shown in Code Example 5-8.

Code Example 5-8 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 ***

When Building a Filter

Notice 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. Deallocate the filter using slapi_filter_free(). Refer to the Directory Server Plug-in Developer's Reference for details.

Normal Directory Server Behavior

Directory Server gets a list of candidate entries, then iterates through the list to check which entries match the search criteria. It then puts the set of results in the parameter block. In most cases, searching from within a plug-in is best done with slapi_search_internal*() calls. Refer to Chapter 7, "Performing Internal Operations," for details.


Extending the Compare Operation

This section shows how to develop functionality called by Directory Server before a client compare operation. Code Example 5-9 logs the target DN and attribute of the entry with which to compare values. For complete example code, refer to ServerRoot/plugins/slapd/slapi/examples/testpreop.c.

Code Example 5-9 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, we 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, meaning it could be binary data such as a JPEG image, and no attempt is made to write the value to the logs.

Code Example 5-10 shows the slapi_pblock_get() call used to obtain the attribute value.

Code Example 5-10 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 as described under Setting Up an Example Suffix and Registering the Plug-In.

Try the example plug-in function using the ldapcompare tool from the Sun Java System Directory Server Resource Kit.

Code Example 5-11 Performing a Comparison 

$ ldapcompare -p port -h host sn:Blinn "uid=pblinn,ou=People,dc=example,dc=com"

comparing type: "sn" value: "Blinn" in entry "uid=pblinn,ou=People,dc=example,dc=com"

compare TRUE

The log entry in ServerRoot/slapd-serverID/logs/errors results as follows, not including housekeeping information at the beginning of the log entry:

Target DN: uid=pblinn,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

The first example discussed here prepends the string "ADD " to the description attribute of the entry to be added to the directory. For complete example code, refer to ServerRoot/plugins/slapd/slapi/examples/testpreop.c.

Before using the plug-in function as described here, set up the example suffix and register the plug-in as described under Setting Up an Example Suffix and Registering the Plug-In.

To try the pre-operation add function, add an entry for Bartholomew's cousin, Quentin, who recently joined Example.com. Quentin's LDIF entry, quentin.ldif, reads as shown in Code Example 5-12.

Code Example 5-12 LDIF for Quentin Cubbins 

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=bcubbins,ou=People,dc=example,dc=com

description: Entry for Quentin Cubbins

Code Example 5-13 performs the prepend.

Code Example 5-13 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 * value;               /* Attribute values        */

    int         i;

    char        * tmp;                 /* Holder for new desc.    */

    const char  * string;              /* Holder for current desc.*/

    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 value of description attribute.             */

    rc |= slapi_entry_attr_find(entry, "description", &attribute);

    if (rc == 0) {                 /* Found a description, so...  */

        for (                          /* ...loop for value...    */

            i = slapi_attr_first_value(attribute, &value);

            i != -1;

            i = slapi_attr_next_value(attribute, i, &value)

        ) {                            /* ...prepend "ADD ".      */

            string = slapi_value_get_string(value);

            tmp    = slapi_ch_malloc(5+strlen(string));

            strcpy(tmp, "ADD ");

            strcpy(tmp+4, string);

            slapi_value_set_string(value, tmp);

            slapi_ch_free((void **)&tmp);

        }

    } 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"

        );

    }

    return 0;

}

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

$ ldapmodify -a -p port -h host -D "cn=Directory Manager" \
-w password -f quentin.ldif

At this point, we can search the directory for Quentin's entry.

Code Example 5-14 Searching for the Entry 

$ ldapsearch -L -p port -h host -b "dc=example,dc=com" "(uid=qcubbins)"

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=bcubbins,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 -p port -h host -D "cn=Directory Manager" -w password \
"uid=qcubbins,ou=People,dc=example,dc=com"

Turn off the pre-operation plug-in to avoid prepending ADD to all the entries you add. Do so from the console on the Configuration tab page by selecting the node bearing the common name of the plug-in and clearing the appropriate checkbox. Restart Directory Server (Tasks tab page in the console) for the change to take effect.

Logging the Entry to Add

The second example discussed here logs the entry to add. For complete example code, refer to ServerRoot/plugins/slapd/slapi/examples/testpostop.c.

Before using the plug-in function as described here, set up the example suffix and register the plug-in as described under Extending the Bind Operation, loading the configuration entry from the introductory comment of testpostop.c. Code Example 5-15 shows that configuration entry.

Code Example 5-15 Configuration Entry (testpostop.c

dn: cn=Test Postop,cn=plugins,cn=config

objectClass: top

objectClass: nsSlapdPlugin

objectClass: extensibleObject

cn: Test Postop

nsslapd-pluginPath: <ServerRoot>/plugins/slapd/slapi/examples/<LibName>

nsslapd-pluginInitfunc: testpostop_init

nsslapd-pluginType: postoperation

nsslapd-pluginEnabled: on

nsslapd-plugin-depends-on-type: database

nsslapd-pluginId: test-postop

nsslapd-pluginVersion: 5.2

nsslapd-pluginVendor: Sun Microsystems, Inc.

nsslapd-pluginDescription: Sample post-operation plug-in

Adjust nsslapd-pluginPath to fit how the product is installed on your system. Notice the value of nsslapd-pluginType: postoperation.

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

Code Example 5-16 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 as specified in Prepending a String to an Attribute.

$ ldapmodify -a -p port -h host -D "cn=Directory Manager" \
-w password -f quentin.ldif

Search ServerRoot/slapd-serverID/logs/errors for the log message. Not including housekeeping information:

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

Notice also the entry logged to changelog.txt, which resembles the LDIF added, except that it has time stamps attached for internal use as shown in Code Example 5-17.

Code Example 5-17 Sample changelog.txt Entry After the Add 

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=bcubbins,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 pre-operation plug-in, not demonstrated here, can be found in ServerRoot/plugins/slapd/slapi/examples/testpreop.c. Refer to ServerRoot/plugins/slapd/slapi/examples/testpostop.c for the source code discussed here.

Before using the plug-in function as described here, set up Directory Server as described under 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, called changelog.txt. The location of changelog.txt depends on the platform, as shown in the source code.

Code Example 5-18 performs the logging.

Code Example 5-18 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. Quentin's entry is specified in Prepending a String to an Attribute.

Code Example 5-19 Checking the Directory for an Entry 

$ ldapsearch -L -p port -h host -b "dc=example,dc=com" "(uid=qcubbins)"

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=bcubbins,ou=People,dc=example,dc=com

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

Code Example 5-20 Modifying a Mail Address 

$ ldapmodify -p port -h host -D "cn=Directory Manager" -w password

dn: uid=qcubbins,ou=People,dc=example,dc=com

changetype: modify

replace: mail

mail: quentin@example.com

^D

Search ServerRoot/slapd-serverID/logs/errors for the log message. Minus housekeeping information:

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

Notice also the information logged to changelog.txt as shown in Code Example 5-21.

Code Example 5-21 Sample changelog.txt Entries After the Modify 

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 pre-operation plug-in, not demonstrated here, can be found in ServerRoot/plugins/slapd/slapi/examples/testpreop.c. Refer to ServerRoot/plugins/slapd/slapi/examples/testpostop.c for the source code discussed here.

Before using the plug-in function as described here, set up Directory Server as described under 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, called changelog.txt. The location of changelog.txt depends on the platform, as shown in the source code.

Code Example 5-22 performs the logging.

Code Example 5-22 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 Code Example 5-19.

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.

Code Example 5-23 Renaming an Entry 

$ ldapmodify -p port -h host -D "cn=Directory Manager" -w password

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 ServerRoot/slapd-serverID/logs/errors for the log message. Not including housekeeping information:

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

Notice also the information logged to changelog.txt as shown in Code Example 5-24.

Code Example 5-24 Sample 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 pre-operation plug-in, not demonstrated here, can be found in ServerRoot/plugins/slapd/slapi/examples/testpreop.c. Refer to ServerRoot/plugins/slapd/slapi/examples/testpostop.c for the source code discussed here.

Before using the plug-in function as described here, set up Directory Server as described under 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, called changelog.txt. The location of changelog.txt depends on the platform, as shown in the source code.

Code Example 5-25 performs the logging.

Code Example 5-25 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 Code Example 5-19.

Quentin's name may be Fred if you have renamed it as described in Extending the Rename Operation.

Imagine last Thursday, Quentin shouted copious verbal abuse at a key customer, embarrassing his brother Bartholomew. As a result Quentin has just been fired from Example.com. With this plug-in activated in Directory Server, delete his entry.

$ ldapdelete -p port -h host -D "cn=Directory Manager" -w password \
"uid=qcubbins,ou=People,dc=example,dc=com"

Search ServerRoot/slapd-serverID/logs/errors for the log message. Not including housekeeping information:

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

Notice also the information logged to changelog.txt as shown in Code Example 5-26.

Code Example 5-26 Sample changelog.txt Entry 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 ServerRoot/plugins/slapd/slapi/examples/testpreop.c for the source code discussed here.

Code Example 5-27 logs the operation number and user DN.

Code Example 5-27 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

        );

    }

    return (rc);

}

Operation identifiers are defined in the plug-in header file ServerRoot/plugins/slapd/slapi/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 as described under Setting Up an Example Suffix and Registering the Plug-In.

With the plug-in activated in Directory Server, perform a search as Yolanda Yorgenson:

$ ldapsearch -L -p port -h host -b "dc=example,dc=com" \
-D "uid=yyorgens,ou=people,dc=example,dc=com" -w yyorgens \
"(uid=bcubbins)"

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

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

The next message reflects the search:

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

Inside plug-in functions, use slapi_op_get_type() to determine the type of an operation. Refer to the Directory Server Plug-in Developer's Reference documentation on slapi_op_get_type() or to the plug-in header file ServerRoot/plugins/slapd/slapi/include/slapi-plugin.h for a list of operation types.



Previous      Contents      Index      Next     


Copyright 2005 Sun Microsystems, Inc. All rights reserved.