Sun Java logo     Previous      Contents      Index      Next     

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

Chapter 9
Writing Extended Operation Plug-Ins

This chapter covers how to write plug-ins to take advantage of LDAP version 3 extended operations, defined in RFC 2251 as allowing "additional operations to be defined for services not available elsewhere in this protocol, for instance digitally signed operations and results."

This chapter includes the following sections:


Calling Extended Operation Plug-Ins

This section describes how extended operations are handled by Directory Server and what requirements are placed on extended operation plug-ins.

Directory Server identifies extended operations by object identifiers (OIDs). Clients request the operation by sending an extended operation request, specifying:

Upon receiving an extended operation request, Directory Server calls the plug-in registered to handle the OID sent in the request. The plug-in function handling the request can obtain the OID and operation-specific data, do its processing, and then send a response to the client containing an OID and any additional data pertaining the extended operation.

When implementing extended operation support, you need to determine the OID to use. The Internet Assigned Numbers Authority can help with registration of official OIDs. If you select your own, know that the key is to avoid OIDs that conflict with other OIDs, such as those defined by the LDAP schema used by Directory Server.

Directory Server determines which extended operation plug-ins handle which extended operation OIDs at startup. At startup, Directory Server calls the initialization function for each plug-in. Extended operation plug-ins register the extended operation OIDs they handle as part of their initialization function, as described in Initializing the Extended Operation Plug-In.

The configuration entry for a plug-in may include parameters that Directory Server passes to the plug-in initialization function at startup, as described in Parameters Specified in the Configuration Entry. You can write the initialization function in such a way that if OIDs are determined after the plug-in functionality is written, they may be passed as configuration entry arguments, for example.


Implementing an Extended Operation Plug-In

This section demonstrates how to implement a basic extended operation plug-in and client application to trigger the extended operation.

The client sends an extended operation request to the server using the OID that identifies the extended operation, in this case 1.2.3.4. The client also sends a value that holds a string. The example plug-in responds to an extended operation request by sending the client an OID and a modified version of the string the client sent with the request.

Finding the Examples

The rest of this chapter refers to the plug-in code in ServerRoot/plugins/slapd/slapi/examples/testextendedop.c, and client code ServerRoot/plugins/slapd/slapi/examples/clients/reqextop.c.

An Example Plug-In

This section explains how our extended operation plug-in works.

Registering the Plug-In

Before using the plug-in function as described here, build and then register the plug-in. Refer to Building Directory Server Plug-Ins for build hints. To register the plug-in, start with the configuration entry from the introductory comment of testextendedop.c. Code Example 9-1 shows the entry.

Code Example 9-1 Configuration Entry (testextendedop.c

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

objectClass: top

objectClass: nsSlapdPlugin

objectClass: extensibleObject

cn: Test ExtendedOp

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

nsslapd-pluginInitfunc: testexop_init

nsslapd-pluginType: extendedop

nsslapd-pluginEnabled: on

nsslapd-plugin-depends-on-type: database

nsslapd-pluginId: test-extendedop

nsslapd-pluginVersion: 5.2

nsslapd-pluginVendor: Sun Microsystems, Inc.

nsslapd-pluginDescription: Sample extended operation plug-in

nsslapd-pluginarg0: 1.2.3.4

Notice the OID 1.2.3.4 is passed as an argument through the configuration entry. The configuration entry could specify more than one nsslapd-pluginarg attribute if the plug-in supported multiple extended operations, each identified by a distinct OID, for example.

To register the plug-in, copy the example configuration entry to a file named testextendedop.ldif, and then adjust nsslapd-pluginPath to fit how the product is installed on your system.

With the correct configuration entry in testextendedop.ldif, load the plug-in. For example:

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

Turn on plug-in logging if you want Directory Server to log informational messages for the plug-in. Refer to Code Example 3-3 for details.

With the plug-in configuration entry correctly loaded in the Directory Server configuration, restart Directory Server to activate the plug-in.

Initializing the Extended Operation Plug-In

As for other plug-in types, extended operation plug-ins include an initialization function that registers other functions in the plug-in with Directory Server. For extended operation plug-ins, this initialization function also registers the OIDs handled by the plug-in. It does this by setting SLAPI_PLUGIN_EXT_OP_OIDLIST in the parameter block Directory Server passes to the initialization function.

Code Example 9-2 demonstrates how the OID list is built and registered.

Code Example 9-2 Registering Plug-In Functions and OIDs (testextendedop.c

#include "slapi-plugin.h"

Slapi_PluginDesc expdesc = {

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

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

    "5.2",                             /* plug-in revision number  */

    "Sample extended operation plug-in"/* plug-in description      */

};

#ifdef _WIN32

__declspec(dllexport)

#endif

int

testexop_init(Slapi_PBlock * pb)

{

    char ** argv;                      /* Args from configuration  */

    int     argc;                      /* entry for plug-in.       */

    char ** oid_list;                  /* OIDs supported           */

    int     rc = 0;                    /* 0 means success          */

    int     i;

    /* Get the arguments from the configuration entry.             */

    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv);

    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc);

    if (rc != 0) {

        slapi_log_info_ex(

            SLAPI_LOG_INFO_AREA_PLUGIN,

            SLAPI_LOG_INFO_LEVEL_DEFAULT,

            SLAPI_LOG_NO_MSGID,

            SLAPI_LOG_NO_CONNID,

            SLAPI_LOG_NO_OPID,

            "testexop_init in test-extendedop plug-in",

            "Could not get plug-in arguments.\n"

        );

        return (rc);

    }

    /* Extended operation plug-ins may handle a range of OIDs.     */

    oid_list = (char **)slapi_ch_malloc((argc + 1) * sizeof(char *));

    for (i = 0; i < argc; ++i) {

        oid_list[i] = slapi_ch_strdup(argv[i]);

        slapi_log_info_ex(

            SLAPI_LOG_INFO_AREA_PLUGIN,

            SLAPI_LOG_INFO_LEVEL_DEFAULT,

            SLAPI_LOG_NO_MSGID,

            SLAPI_LOG_NO_CONNID,

            SLAPI_LOG_NO_OPID,

            "testexop_init in test-extendedop plug-in",

            "Registering plug-in for extended operation %s.\n",

            oid_list[i]

        );

    }

    oid_list[argc] = NULL;

        

    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 *) &expdesc

    );

    rc |= slapi_pblock_set(            /* Extended op. handler     */

        pb,

        SLAPI_PLUGIN_EXT_OP_FN,

        (void *) test_extendedop

    );

    rc |= slapi_pblock_set(            /* List of OIDs handled     */

        pb,

        SLAPI_PLUGIN_EXT_OP_OIDLIST,

        oid_list

    );

    return (rc);

}

Notice here we extract OIDs from the arguments passed by Directory Server from the configuration entry into the parameter block, using slapi_ch_strdup() on each argv[] element. The OID list is then built by allocating space for the array using slapi_ch_malloc() and placing the OIDs in each oid_list[] element. We then register the plug-in OID list using SLAPI_PLUGIN_EXT_OP_OIDLIST. We register the extended operation handler function, test_extendedop(), using SLAPI_PLUGIN_EXT_OP_FN as shown.

Refer to the Directory Server Plug-in Developer's Reference for more information about parameter block arguments and plug-in API functions.

Handling the Extended Operation

The plug-in function test_extendedop() gets the OID and value for the operation from the client request. It then sends the client a response, as shown in Code Example 9-3.

Code Example 9-3 Responding to an Extended Operation Request (testextendedop.c

#include "slapi-plugin.h"

int

test_extendedop(Slapi_PBlock * pb)

{

    char          * oid;               /* Client request OID       */

    struct berval * client_bval;       /* Value from client        */

    char          * result;            /* Result to send to client */

    char          * tmp_msg;

    struct berval * result_bval;       /* Encoded result           */

    int             connId, opId, rc = 0;

    long            msgId;

    /* Identify the request for logging.                           */

    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,

            SLAPI_LOG_NO_MSGID,

            SLAPI_LOG_NO_CONNID,

            SLAPI_LOG_NO_OPID,

            "test_extendedop in test-extendedop plug-in",

            "Could not identify message, connection or operation.\n"

        );

    }

    /* Get the OID and the value included in the request.          */

    rc |= slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID,   &oid );

    rc |= slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &client_bval);

    if (rc != 0) {

        tmp_msg = "Could not get OID and value from request";

        slapi_log_info_ex(

            SLAPI_LOG_INFO_AREA_PLUGIN,

            SLAPI_LOG_INFO_LEVEL_DEFAULT,

            msgId,

            connId,

            opId,

            "test_extendedop in test-extendedop plug-in",

            "%s\n", tmp_msg

        );

        slapi_send_ldap_result(

            pb,                        /* PBlock for request       */

            LDAP_OPERATIONS_ERROR,     /* LDAP result code         */

            NULL,                      /* For LDAP_NO_SUCH_OBJECT  */

            tmp_msg,                   /* Text message for client  */

            0,                         /* Number of entries sent   */

            NULL                       /* URL for referral         */

        );

        return (SLAPI_PLUGIN_EXTENDED_SENT_RESULT);

    } else {

        slapi_log_info_ex(

            SLAPI_LOG_INFO_AREA_PLUGIN,

            SLAPI_LOG_INFO_LEVEL_DEFAULT,

            msgId,

            connId,

            opId,

            "test_extendedop in test-extendedop plug-in",

            "Request with OID: %s  Value from client: %s\n",

            oid, client_bval->bv_val

        );

    }

    /*

     * Set the value to return to the client, depending on what your

     * plug-in function does. Here, we return the value sent by the

     * client, prefixed with the string "Value from client: ".     */

    tmp_msg             = "Value from client: ";

    result              = (char *)slapi_ch_malloc(

        client_bval->bv_len + strlen(tmp_msg) + 1);

    sprintf(result, "%s%s", tmp_msg, client_bval->bv_val);

    result_bval         = (struct berval *)slapi_ch_malloc(

        sizeof(struct berval));

    result_bval->bv_val = result;

    result_bval->bv_len = strlen(result_bval->bv_val);

    /*

     * Prepare the PBlock to return and OID and value to the client.

     * Here, we demonstrate that the plug-in may return a different

     * OID than the one sent by the client. You may, for example,

     * use the different OID to indicate something to the client.  */

    rc |= slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID,   "5.6.7.8");

    rc |= slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, result_bval);

    if (rc != 0) {

        slapi_ch_free((void **)&result);

        tmp_msg = "Could not set results to return to client.";

        slapi_log_info_ex(

            SLAPI_LOG_INFO_AREA_PLUGIN,

            SLAPI_LOG_INFO_LEVEL_DEFAULT,

            msgId,

            connId,

            opId,

            "test_extendedop in test-extendedop plug-in",

            "%s\n", tmp_msg

        );

        slapi_send_ldap_result(

            pb,

            LDAP_OPERATIONS_ERROR,

            NULL,

            tmp_msg,

            0,

            NULL

        );

        return (SLAPI_PLUGIN_EXTENDED_SENT_RESULT);

    }

    /* Send the result to the client.                              */

    slapi_send_ldap_result(

        pb,

        LDAP_SUCCESS,

        NULL,

        "Extended operation successful!",

        0,

        NULL

    );

    slapi_log_info_ex(

        SLAPI_LOG_INFO_AREA_PLUGIN,

        SLAPI_LOG_INFO_LEVEL_DEFAULT,

        msgId,

        connId,

        opId,

        "test_extendedop in test-extendedop plug-in",

        "OID sent to client: %s  Value sent to client: %s\n",

        "5.6.7.8", result

    );

    slapi_ch_free((void **)&result);

    /* Tell the server we sent the result.                         */

    return (SLAPI_PLUGIN_EXTENDED_SENT_RESULT);

}

Notice how the function obtains the OID and value from the request using SLAPI_EXT_OP_REQ_OID and SLAPI_EXT_OP_REQ_VALUE. It then uses slapi_ch_malloc() to construct a string to return to the client through the pointer to a berval structure, result_bval. A different extended operation plug-in might do something entirely different at this point.

Also notice the function sends a different OID back to the client than the OID in the client request. The OID sent back can be used to indicate a particular result to the client, for example. The function uses slapi_send_ldap_result() to indicate success and send the OID and value to the client, frees the memory allocated, then returns SLAPI_PLUGIN_EXTENDED_SENT_RESULT to indicate to Directory Server that processing of the plug-in function is complete.

If the function had not sent a result code to the client, it would return an LDAP result code to Directory Server, which Directory Server would then send to the client.

If the function cannot handle the extended operation with the specified OID, it returns SLAPI_PLUGIN_EXTENDED_NOT_HANDLED, after which Directory Server sends an LDAP_PROTOCOL_ERROR result code to the client.

Developing the Client

To test our plug-in, we need a client that requests an extended operation with OID 1.2.3.4. The example discussed here, reqextop.c, is delivered with the product. The Sun Java System Directory SDK for C is used to build the client.

The client sets up a short string to send to Directory Server in the extended operation request. It then gets an LDAP connection that supports LDAP version 3, and binds to Directory Server. The client then sends an extended operation request and displays the result on STDOUT. Code Example 9-4 shows how the extended operation request is handled on the client side.

Code Example 9-4 Client Requesting an Extended Operation (clients/reqextop.c

#include <stdlib.h>

#include "ldap.h"

/* Global variables for client connection information.

* You may set these here or on the command line.                  */

static char * host     = "localhost";  /* Server hostname          */

static int    port     = 389;          /* Server port              */

static char * bind_DN  = "cn=Directory Manager"; /* DN to bind as  */

static char * bind_pwd = "23skidoo";   /* Password for bind DN     */

/* Check for connection info as command line arguments.            */

int get_user_args(int argc, char ** argv);

int

main(int argc, char ** argv)

{

    /* OID of the extended operation that you are requesting       */

    const char    * oidrequest = "1.2.3.4"; /* Ext op OID          */

    char          * oidresult;         /* OID in reply from server */

    struct berval   valrequest;        /* Request sent             */

    struct berval * valresult;         /* Reply received           */

    LDAP          * ld;                /* Handle to connection     */

    int             version;           /* LDAP version             */

    /* Use default connection arguments unless all four are

     * provided as arguments on the command line.                  */

    if (get_user_args(argc, argv) != 0) return 1; /* Usage error   */

    /* Set up the value that you want to pass to the server        */

    printf("Setting up value to pass to server...\n");

    valrequest.bv_val = "My Value";

    valrequest.bv_len = strlen("My Value");

    /* Get a handle to an LDAP connection                          */

    printf("Getting the handle to the LDAP connection...\n");

    if ((ld = ldap_init(host, port)) == NULL) {

        perror("ldap_init");

        ldap_unbind(ld);

        return 1;

    }

    /* Set the LDAP protocol version supported by the client

       to 3. (By default, this is set to 2. Extended operations

       are part of version 3 of the LDAP protocol.)                */

    printf("Resetting version %d to 3.0...\n", version);

    version = LDAP_VERSION3;

    ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &version);

    /* Authenticate to the directory as the Directory Manager      */

    printf("Binding to the directory...\n");

    if (ldap_simple_bind_s(ld, bind_DN, bind_pwd) != LDAP_SUCCESS) {

        ldap_perror(ld, "ldap_simple_bind_s");

        ldap_unbind(ld);

        return 1;

    }

    /* Initiate the extended operation                             */

    printf( "Initiating the extended operation...\n" );

    if (ldap_extended_operation_s(

            ld,

            oidrequest,

            &valrequest,

            NULL,

            NULL,

            &oidresult,

            &valresult

        ) != LDAP_SUCCESS) {

        ldap_perror(ld, "ldap_extended_operation_s failed: ");

        ldap_unbind(ld);

        return 1;

    }

    /* Get OID and value from result returned by server.           */

    printf("Operation successful.\n");

    printf("\tReturned OID: %s\n", oidresult);

    printf("\tReturned value: %s\n", valresult->bv_val);

    /* Disconnect from the server.                                 */

    ldap_unbind(ld);

    return 0;

}

Notice the client identifies the request by OID, and that it resets the protocol version to LDAP_VERSION3 to ensure extended operation support in the protocol. The value the client sends with the request, valrequest, points to a berval structure. Also notice that the calls used here for the bind and extended operation are synchronous. Asynchronous versions are also available.

Trying It Out

With the plug-in active in the server, compile the client reqextop.c. One way of doing this with the Sun Java System Directory SDK for C involves adding a reqextop target to lib/ldapcsdk/examples/Makefile. For example:

Code Example 9-5 Makefile Additions to Build the Extended Operation Client 

reqextop: reqextop.o

    $(CC) -o reqextop reqextop.o $(LIBS)

Next, copy reqextop.c to the lib/ldapcsdk/examples/ directory and build the client. The Makefile works with GNU Make. For example:

Code Example 9-6 Building the Extended Operation Client 

$ cd sdk_install_dir/lib/ldapcsdk/examples

$ cp ServerRoot/plugins/slapd/slapi/examples/clients/reqextop.c \

sdk_install_dir/lib/ldapcsdk/examples

$ gmake reqextop

Next, run the client to send the extended operation request and display the result.

Code Example 9-7 Client Side Extended Operation Results 

$ ./reqextop -p port -w password

Using the following connection info:

        host:    localhost

        port:    port

        bind DN: cn=Directory Manager

        pwd:     password

Setting up value to pass to server...

Getting the handle to the LDAP connection...

Resetting version 2 to 3.0...

Binding to the directory...

Initiating the extended operation...

Operation successful.

        Returned OID: 5.6.7.8

        Returned value: Value from client: My Value

On the Directory Server side, if you have turned on logging, messages similar to the following in the errors log show that the plug-in handled the client request:

[22/May/2112:08:54:15 +0200] - INFORMATION - test_extendedop in test-extendedop plug-in - conn=0 op=1 msgId=2 - Request with OID: 1.2.3.4 Value from client: My Value
[22/May/2112:08:54:15 +0200] - INFORMATION - test_extendedop in test-extendedop plug-in - conn=0 op=1 msgId=2 - OID sent to client: 5.6.7.8 Value sent to client: Value from client: My Value

We have thus demonstrated that our example extended operation plug-in handles requests for the extended operation with OID 1.2.3.4.



Previous      Contents      Index      Next     


Copyright 2005 Sun Microsystems, Inc. All rights reserved.