/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved
 * Use of this product is subject to license terms.
 */
/************************************************************

 internal.c

 This source file demonstrates the use of internal operations in
 a server plug-in.

 Before using this plug-in, create a suffix "dc=example,dc=com",
 then populate the suffix with data from the LDIF example file
 <install-path>/ldif/Example.ldif.

 ACTIVATING THIS PLUG-IN
 -----------------------
 1. Build the library containing the plug-in.
 
 2. Configure the server to log plug-in messages.

    $ dsconf set-log-prop error level:err-plugins

 3. Configure the server to load the plug-in.
     
    $ dsconf create-plugin -H <lib-path> -F internal_init -Y object "Test internal"
    $ dsconf set-plugin-prop "Test internal" feature:test-internal version:7.0 desc:"Sample internal operations plug-in"
    $ dsconf enable-plugin "Test internal"
    Directory Server needs to be restarted for changes to take effect
    $ dsadm restart <inst-path>

************************************************************/
#include <stdio.h>
#include <string.h>
#include "slapi-plugin.h"

Slapi_PluginDesc           internal_desc = {
    "test-internal",                     /* plug-in identifier      */
    "Oracle Corporation (test)",         /* vendor name             */
    "11.1.1.3.0",                        /* plug-in revision number */
    "Sample internal operations plug-in" /* description             */
};

/* Internal operations require an ID for the plug-in.                   */
static Slapi_ComponentId * plugin_id     = NULL;

/*
 * Internal searches invoke callbacks which let you pass data through
 * the internal operation. The plug-in defines the data structure. In
 * this case, we want to get an LDIF representation of the entry found
 * by the search, and so set up fields corresponding to the parameters
 * of slapi_entry2str(). The internal search returns the entry found
 * to the callback function.                                            */
struct cb_data {                       /* Data returned from search     */
    char * e_str;                      /* Entry as LDIF                 */
    int    e_len;                      /* Length of LDIF                */
};

/* Get an LDIF representation of the entry found by the internal search.*/
int
test_internal_entry_callback(Slapi_Entry * entry, void * callback_data)
{
    struct cb_data * data;
    int              len;

    /* This callback could do something more interesting with the
     * data such as build an array of entries returned by the search.
     * Here we simply log the result.                                   */
    data        = (struct cb_data *)callback_data;
    data->e_str = slapi_entry2str(entry, &len);
    data->e_len = len;
    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_internal_entry_callback in test-internal plug-in",
        "\nFound entry: %sLength: %d\n", data->e_str, data->e_len
    );
    return (-1);                       /* Stop at the first entry.      */
}                                      /* To continue, return 0.        */

/*
 * Manipulate an entry under ou=people,dc=example,dc=com at server
 * startup. If no back end for dc=example,dc=com exists, the plug-in
 * does nothing.                                                        */
int
test_internal()
{
    Slapi_DN       * sdn;              /* DN holder for internal ops    */
    Slapi_Entry    * entry;            /* Entry holder for internal ops */
    Slapi_PBlock   * pb;               /* PBlock for internal ops       */
    char           * str = NULL;       /* String holder for internal ops*/
    int              len;              /* Length of LDIF from entry     */
    LDAPMod          mod_attr;         /* Attribute to modify           */
    LDAPMod        * mods[2];          /* Array of modifications        */
    char           * mail_vals[]  =    /* New mail address              */
                       {"quentin@example.com", NULL};
    char           * srch_attrs[] =    /* Attr. to get during search    */
                       {LDAP_ALL_USER_ATTRS, NULL};
    struct cb_data   callback_data;    /* Data returned from search     */
    int              rc;               /* Return code; 0 means success. */

    /* Check that the example suffix exists.                            */
    sdn = slapi_sdn_new_dn_byval("dc=example,dc=com");
    if (slapi_be_exist(sdn)) {
        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_internal in test-internal plug-in",
            "Suffix (%s) does not exist, exiting.\n",
            slapi_sdn_get_dn(sdn)
        );
        slapi_sdn_free(&sdn);
        return (0);
    }
    slapi_sdn_free(&sdn);

    /*
     * Add an entry for Quentin Cubbins to the example suffix
     * using slapi_add_entry_internal().                                */
    entry = slapi_entry_alloc();
    slapi_entry_set_dn(                /* slapi_entry_set_dn()          */
        entry,                         /* requires a copy of the DN.    */
        slapi_ch_strdup("uid=qcubbins,ou=People,dc=example,dc=com")
    );
                                       /* slapi_entry_add_string()      */
                                       /* does not require a copy.      */
    slapi_entry_add_string(entry, "objectclass", "top");
    slapi_entry_add_string(entry, "objectclass", "person");
    slapi_entry_add_string(entry, "objectclass", "organizationalPerson");
    slapi_entry_add_string(entry, "objectclass", "inetOrgPerson");
    slapi_entry_add_string(entry, "uid", "qcubbins");
    slapi_entry_add_string(entry, "givenName", "Quentin");
    slapi_entry_add_string(entry, "sn", "Cubbins");
    slapi_entry_add_string(entry, "cn", "Quentin Cubbins");
    slapi_entry_add_string(entry, "mail", "qcubbins@example.com");
    slapi_entry_add_string(entry, "userPassword", "arbitrary");
    slapi_entry_add_string(entry, "secretary",
        "uid=bjensen,ou=People,dc=example,dc=com");

    pb = slapi_pblock_new();           /* Make our own PBlock...        */
    rc = slapi_add_entry_internal_set_pb(
        pb,
        entry,
        NULL,                          /* No controls                   */
        plugin_id,
        SLAPI_OP_FLAG_NEVER_CHAIN      /* Never chain this operation.   */
    );
    if (rc != 0) {
        slapi_pblock_destroy(pb);
        return (-1);            
    }

    str = slapi_entry2str(entry, &len);/* Entry as LDIF for the log.
                                        * Note we have to capture this
                                        * before the internal add, during
                                        * which the entry is consumed.  */
    rc = slapi_add_internal_pb(pb);    /* Entry consumed here           */
                                       /* ... get status ...            */
    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);  
        return (-1);            
    }

    slapi_pblock_destroy(pb);   

    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_internal in test-internal plug-in",
        "\nAdded entry:\n%sEntry length: %d\n", str, len 
    );
    
    /* Modify Quentin's entry after his email address changes from
     * qcubbins@example.com to quentin@example.com.                     */
    mod_attr.mod_type   = "mail";
    mod_attr.mod_op     = LDAP_MOD_REPLACE;
    mod_attr.mod_values = mail_vals;   /* mail: quentin@example.com     */
    mods[0]             = &mod_attr;
    mods[1]             = NULL;

    /* For slapi_*_internal_set_pb() calls, we identify entries either
     * by DN, or by unique ID. If you have a choice, using DNs is
     * slightly faster, because using unique IDs requires that the
     * server perform a search to find the entry. Note, however, that
     * the difference is minimal for write operations, because the
     * server can complete the search in only a fraction of the amount
     * of time needed for the write.                                    */
    pb = slapi_pblock_new();           /* Set up a PBlock...            */
    rc = slapi_modify_internal_set_pb(
        pb,
        "uid=qcubbins,ou=people,dc=example,dc=com",
        mods,
        NULL,                          /* No controls                   */
        NULL,                          /* DN rather than unique ID      */
        plugin_id,
        SLAPI_OP_FLAG_NEVER_CHAIN      /* Never chain this operation.   */
    );
    if (rc != 0) {
        slapi_pblock_destroy(pb);
        return (-1);            
    }

    rc = slapi_modify_internal_pb(pb); /* Unlike internal add,          */
                                       /* nothing consumed here         */
                                       /* ... get status ...            */
    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);          
        return (-1);            
    }
    
    slapi_pblock_destroy(pb);          /* ... clean up the PBlock.      */

    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_internal in test-internal plug-in",
        "\nModified attribute: %s\nNew value: %s\n",
        mod_attr.mod_type, mail_vals[0] 
    );

    /* Rename the entry when Quentin decides people should call him
     * Fred instead of Quentin.                                         */
    pb = slapi_pblock_new();           /* Set up a PBlock again...      */
    rc = slapi_rename_internal_set_pb(
        pb,
        "uid=qcubbins,ou=people,dc=example,dc=com",
        "uid=fcubbins",
        "ou=people,dc=example,dc=com",
        1,                             /* Delete old RDN                */
        NULL,                          /* No controls                   */
        NULL,                          /* DN rather than unique ID      */
        plugin_id,
        SLAPI_OP_FLAG_NEVER_CHAIN      /* Never chain this operation.   */
    );

    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);          
        return (-1);            
    }

    rc = slapi_modrdn_internal_pb(pb); /* Like internal modify,         */
                                       /* nothing consumed here.        */
                                       /* ... get status ...            */
    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);          
        return (-1);
    }

    slapi_pblock_destroy(pb);          /* ... cleaning up.              */

    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_internal in test-internal plug-in",
        "\nNew entry RDN: %s\n", "uid=fcubbins"
    );

    /* Find Fred (Quentin's) renamed entry and log the result.
     * To log the result, we let the internal search trigger a
     * callback when it finds the entry. Then we let the callback
     * log the entry.                                                   */
    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_internal in test-internal plug-in",
        "\nSearching with base DN %s, filter %s...\n",
        "dc=example,dc=com", "(uid=fcubbins)"
    );

    pb = slapi_pblock_new();           /* Set up a PBlock...            */
    rc = slapi_search_internal_set_pb(
        pb,
        "dc=example,dc=com",           /* Base DN for search            */
        LDAP_SCOPE_SUBTREE,            /* Scope                         */
        "(uid=fcubbins)",              /* Filter                        */
        srch_attrs,                    /* Set to get all user attrs.    */
        0,                             /* Return attrs. and values      */
        NULL,                          /* No controls                   */
        NULL,                          /* DN rather than unique ID      */
        plugin_id,
        SLAPI_OP_FLAG_NEVER_CHAIN      /* Never chain this operation.   */
    );

    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);          
        return (-1);            
    }

    /* Internal search puts results into the PBlock, but we use callbacks
     * to get at the data as it is turned up by the search. In this case,
     * what we want to get is the entry found by the search.            */
    rc  = slapi_search_internal_pb(pb);
    rc |= slapi_search_internal_callback_pb(
        pb,
        &callback_data,
        NULL,                          /* No result callback            */
        test_internal_entry_callback,
        NULL                           /* No referral callback          */
    );

                                       /* ... get status ...            */
    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);          
        return -1;
    }
                                       /* Free the search results when
                                        * finished with them.           */
    slapi_free_search_results_internal(pb);
    slapi_pblock_destroy(pb);          /* ... done cleaning up.         */

    /* Delete the entry we have been manipulating.                      */
    pb = slapi_pblock_new();           /* Set up a PBlock...            */
    rc = slapi_delete_internal_set_pb(
        pb,
        "uid=fcubbins,ou=people,dc=example,dc=com",
        NULL,                          /* No controls                   */
        NULL,                          /* DN rather than unique ID      */
        plugin_id,
        SLAPI_OP_FLAG_NEVER_CHAIN      /* Never chain this operation.   */
    );

    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);          
        return -1;
    }

    rc = slapi_delete_internal_pb(pb); /* Does not consume any          */
                                       /* data set in the PBlock.       */
                                       /* ... get status ...            */
    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    if (rc != LDAP_SUCCESS) {
        slapi_pblock_destroy(pb);          
        return -1;
    }


    slapi_pblock_destroy(pb);          /* ... cleaning up.              */

    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_internal in test-internal plug-in",
        "\nDeleted entry with DN: %s\n",
        "uid=fcubbins,ou=people,dc=example,dc=com"
    );

    return (0);
}

/* Register the plug-in with the server                                 */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
internal_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 *) &internal_desc
    );
    rc |= slapi_pblock_set(            /* Startup function              */
        pb,
        SLAPI_PLUGIN_START_FN,
        (void *) test_internal
    );
    /* Get plug-in identity to pass to internal operations.             */
    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id);
    return rc;
}
