Sun Directory Server Enterprise Edition 7.0 Developer's Guide

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 by 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. The plug-in also sends a modified version of the string that the client sent with the request.

Locating the Extended Operation Examples

The rest of this chapter refers to the plug-in code in install-path/examples/testextendedop.c, and the client code in install-path/examples/clients/reqextop.c.

Example Extended Operation Plug-In

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

Registering the Extended Operation Plug-In

Before using the plug-in function as described here, build the plug-in. Also, configure Directory Server to load the plug-in.

Notice that 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.

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.


     $ dsconf create-plugin -F custom-plugin-init-function -G custom-plugin-argument -H lib-path \
    -Y custom-plugin-type "Custom Plugin"
    $ dsconf enable-plugin "Custom Plugin"

    Hint For more information, use the commands specified in the plug-in source file.

  3. Restart Directory Server.


    $ dsadm restart instance-path
    

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. The function registers OIDs by setting SLAPI_PLUGIN_EXT_OP_OIDLIST in the parameter block Directory Server, which the function passes to the initialization function.


Example 9–1 Registering Plug-In Functions and OIDs (testextendedop.c)

This example demonstrates how the OID list is built and registered.

#include "slapi-plugin.h"

Slapi_PluginDesc expdesc = {
    "test-extendedop",                 /* plug-in identifier       */
    "Sun Microsystems, Inc.",          /* vendor name              */
    "6.0",                             /* 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 that you extract OIDs from the arguments passed by Directory Server. Directory Server passes the arguments from the configuration entry to the parameter block, by using slapi_ch_strdup() on each argv[] element. The OID list is then built by allocating space for the array by using slapi_ch_malloc() and placing the OIDs in each oid_list[] element. You then register the plug-in OID list by using SLAPI_PLUGIN_EXT_OP_OIDLIST. You register the extended operation handler function, test_extendedop(), using SLAPI_PLUGIN_EXT_OP_FN as shown.

Refer to Part II, Directory Server Plug-In API Reference for details 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. The function then sends the client a response, as shown in Developing the Extended Operation Client.

Notice how the function obtains the OID and value from the request by using SLAPI_EXT_OP_REQ_OID and SLAPI_EXT_OP_REQ_VALUE. The function 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 that the function sends a different OID back to the client than the OID in the client request. The OID that is 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 as well as to send the OID and value to the client. The function then frees the memory that was allocated. Finally, the function 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. Directory Server would then send the result code to the client.

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

Developing the Extended Operation Client

To test the plug-in, you 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 client sets up a short string to send to Directory Server in the extended operation request. The client 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.


Example 9–2 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;
}

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


Trying the Extended Operation Example

  1. After activating the plug-in in the server, compile the client code reqextop.c.

  2. Add an entry under cn=features in the configuration to let users access the extended operation.


    $ cat myextop.ldif 
    dn: oid=1.2.3.4,cn=features,cn=config
    objectClass: top
    objectClass: directoryServerFeature
    oid: 1.2.3.4
    cn: Fake extended operation
    aci: (targetattr != "aci")(version 3.0;acl "Fake extended operation"; allow
     ( read, search, compare, proxy ) userdn = "ldap:///all";)
    
    $ ldapmodify -h localhost -p 389 -a -D cn=directory\ manager -w - -f myextop.ldif
    Enter bind password: 
    adding new entry uid=qcubbins,ou=People,dc=example,dc=com
    
    $ dsadm restart /local/ds
    Waiting for server to stop...
    Server stopped
    Server started: pid=5658
    $ 
  3. Run the client to send the extended operation request and display the result.


    $ ./reqextop -w password
    Using the following connection info:
            host:    localhost
            port:    389
            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
  4. On the Directory Server side, turn 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

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