/*
 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved.
 * Use is subject to license terms. 
 * Some preexisting portions Copyright 1997-2001 Netscape Communications Corp.
 * All rights reserved.
 */
/************************************************************

 testpreop.c

 This source file provides examples of pre-operation plug-in
 functions.  The server calls these plug-in functions before
 executing certain LDAP operations:

 * testpreop_bind    (called before an LDAP bind operation)
   Logs target DN and authentication type for the bind.
   
 * testpreop_search  (called before an LDAP search operation)
   Breaks down and logs information about the search request.

 * testpreop_add     (called before an LDAP add operation)
   Prepends ADD to the description of the entry to be added.

 * testpreop_cmp     (called before an LDAP compare operation)
   Logs the DN and attribute type to be used for comparison.

 * testpreop_del     (called before an LDAP delete operation)
   Logs the DN of the entry to delete.

 * testpreop_mod     (called before an LDAP modify operation)
   Logs the DN of the entry to modify.

 * testpreop_modrdn  (called before an LDAP modrdn operation)
   Logs the DN of the entry to rename, and the new RDN to assign.

 * testpreop_abandon (called before an LDAP abandon operation)
   Logs the user authenticated on the connection for the abandon.

 * testpreop_send    (called before sending something to the client)
   Logs the user to whom the information is being sent.

 NOTE The Directory Server front-end handles bind operations requested
 by the root DN. The server does not invoke the pre-bind function if
 the client is authenticating as the root DN.

 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 testpreop_init -Y preoperation "Test Preop"
    $ dsconf set-plugin-prop "Test Preop" depends-on-type:database feature:test-preop version:7.0 desc:"Sample pre-operation plug-in"
    $ dsconf enable-plugin "Test Preop"
    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 preop_desc = {
    "test-preop",                      /* plug-in identifier      */
    "Oracle Corporation (test)",       /* vendor name             */
    "11.1.1.3.0",                      /* plug-in revision number */
    "Sample pre-operation plug-in"     /* plug-in description     */
};

/* Log target DN and authentication type for the bind.            */
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);
}

#define LOG1(format) \
        (slapi_log_info_ex( \
            SLAPI_LOG_INFO_AREA_PLUGIN, \
            SLAPI_LOG_INFO_LEVEL_DEFAULT, \
            msgId, \
            connId, \
            opId, \
            "testpreop_search in test-preop plug-in", \
            format))
#define LOG2(format, var) \
        (slapi_log_info_ex( \
            SLAPI_LOG_INFO_AREA_PLUGIN, \
            SLAPI_LOG_INFO_LEVEL_DEFAULT, \
            msgId, \
            connId, \
            opId, \
            "testpreop_search in test-preop plug-in", \
            format, var))
#define LOG3(format, var1, var2) \
        (slapi_log_info_ex( \
            SLAPI_LOG_INFO_AREA_PLUGIN, \
            SLAPI_LOG_INFO_LEVEL_DEFAULT, \
            msgId, \
            connId, \
            opId, \
            "testpreop_search in test-preop plug-in", \
            format, var1, var2))

/* Break down and log information about the search request.       */
int
testpreop_search(Slapi_PBlock * pb)
{
    char          *  base       = NULL;/* Base DN for search      */
    char          *  filter_str = NULL;/* Search filter as string */
    char          *  attr_type  = NULL;/* For substr and compare  */
    char          *  substr_init= NULL;/* For substring filters   */
    char          *  substr_final=NULL;
    char          ** substr_any = NULL;
    int              scope;            /* Base, 1 level, subtree  */
    int              deref;            /* When does deferencing occur? */
    int              filter_type; 
    int              i;
    Slapi_Filter  *  filter;
    struct berval *  bval;
    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);
    if (rc == 0) {
        LOG1("*** PREOPERATION SEARCH PLUG-IN - START ***\n");
    } else {
        return (rc);
    }

    /* Log base DN and scope for search. */
    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;
        }
    } else {
        return (rc);
    }
    
    /* Log alias dereferencing settings for search. */
    rc |= slapi_pblock_get(pb, SLAPI_SEARCH_DEREF,     &deref);
    if (rc == 0) {
        switch (deref) {
        case LDAP_DEREF_NEVER:
            LOG1("Dereference setting: LDAP_DEREF_NEVER\n");
            break;
        case LDAP_DEREF_SEARCHING:
            LOG1("Dereference setting: LDAP_DEREF_SEARCHING\n");
            break;
        case LDAP_DEREF_FINDING:
            LOG1("Dereference setting: LDAP_DEREF_FINDING\n");
            break;
        case LDAP_DEREF_ALWAYS:
            LOG1("Dereference setting: LDAP_DEREF_ALWAYS\n");
            break;
        default:
            LOG2("Dereference setting: unknown value %d\n", deref);
            break;
        }
    } else {
        return (rc);
    }

    /* Log search filter information. */
    rc |= slapi_pblock_get(pb, SLAPI_SEARCH_FILTER,    &filter);
    rc |= slapi_pblock_get(pb, SLAPI_SEARCH_STRFILTER, &filter_str);
    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;
        }
    } else {
        return (rc);
    }
    if (                               /* Attr for comparisons.   */
        (filter_type == LDAP_FILTER_EQUALITY) ||
        (filter_type == LDAP_FILTER_GE) ||
        (filter_type == LDAP_FILTER_LE) ||
        (filter_type == LDAP_FILTER_APPROX)
    ) {
        slapi_filter_get_ava(filter, &attr_type, &bval);
        if ((attr_type != NULL) && (bval->bv_val != NULL)) {
            LOG2("\tAttribute type: %s\n", attr_type);
            LOG2("\tAttribute value: %s\n", bval->bv_val);
        }
    }
    LOG2("String representation of search filter: %s\n", filter_str);

    LOG1("*** PREOPERATION SEARCH PLUG-IN - END ***\n");
    return (rc);
}

/* Prepend ADD to each description value of the entry to be added.*/
int
testpreop_add(Slapi_PBlock * pb)
{
    Slapi_Entry * entry;               /* Entry to add            */
    Slapi_Attr  * attribute;           /* Entry attributes        */
    Slapi_Value ** values;             /* Modified, duplicate vals*/
    int         connId, opId, rc = 0;
    long        msgId;

    /* Get the entry. */
    rc |= slapi_pblock_get(pb, SLAPI_ADD_ENTRY,       &entry);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_MSGID, &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID,         &connId);
    rc |= slapi_pblock_get(pb, SLAPI_OPERATION_ID,    &opId);

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

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

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

        values[count] = NULL;

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

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

    return 0;
}

/* Log target DN and attribute type to be used for comparison.    */
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);
}

/* Log target DN of entry to delete.                              */
int
testpreop_del(Slapi_PBlock * pb)
{
    char * dn;                         /* Target DN               */
    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);
    if (rc == 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_del in test-preop plug-in",
            "Target DN: %s\n", dn
        );
    }

    return (rc);
}

/* Log target DN of entry to modify.                              */
int
testpreop_mod(Slapi_PBlock * pb)
{
    char * dn;                         /* Target DN               */
    int    connId, opId, rc = 0;
    long   msgId;

    rc |= slapi_pblock_get(pb, SLAPI_MODIFY_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);
    if (rc == 0) {
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "testpreop_mod in test-preop plug-in",
            "Target DN: %s\n", dn
        );
    }

    return (rc);
}

/* Log target DN of entry to rename, and new RDN to assign.       */
int
testpreop_modrdn(Slapi_PBlock * pb)
{
    char * dn;                         /* Target DN               */
    char * new_rdn;
    int    connId, opId, rc = 0;
    long   msgId;

    rc |= slapi_pblock_get(pb, SLAPI_MODRDN_TARGET,   &dn);
    rc |= slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN,   &new_rdn);
    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_modrdn in test-preop plug-in",
            "Target DN: %s\tNew RDN: %s\n", dn, new_rdn
        );
    }

    return (rc);
}

/* Log the user authenticated on connection for this operation.   */
int
testpreop_abandon(Slapi_PBlock * pb)
{
    unsigned long   msgId;
    int             connId, opId, rc = 0;
    char          * connDn;

    rc |= slapi_pblock_get(pb, SLAPI_ABANDON_MSGID, &msgId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_ID, &connId);
    rc |= slapi_pblock_get(pb, SLAPI_CONN_DN, &connDn);
    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_abandon in test-preop plug-in",
            "Operation being abandoned. User: %s\n", connDn
        );
    }
    slapi_ch_free_string(&connDn);

    return (rc);
}

/* Log the user to whom the information is being sent.            */
int
testpreop_send(Slapi_PBlock * pb)
{
    Slapi_Operation * op;              /* Operation in progress   */
    char            * connDn;          /* Get DN from connection  */
    int               connId, opId, rc = 0;
    long              msgId;

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

    return (rc);
}

/* Register the plug-in with the server.                          */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
testpreop_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 *) &preop_desc
    );
    rc |= slapi_pblock_set(            /* Pre-op bind function    */
        pb,
        SLAPI_PLUGIN_PRE_BIND_FN,
        (void *) testpreop_bind
    );
    rc |= slapi_pblock_set(            /* Pre-op search function  */
        pb,
        SLAPI_PLUGIN_PRE_SEARCH_FN,
        (void *) testpreop_search
    );
    rc |= slapi_pblock_set(            /* Pre-op add function     */
        pb,
        SLAPI_PLUGIN_PRE_ADD_FN,
        (void *) testpreop_add
    );
    rc |= slapi_pblock_set(            /* Pre-op compare function */
        pb,
        SLAPI_PLUGIN_PRE_COMPARE_FN,
        (void *) testpreop_cmp
    );
    rc |= slapi_pblock_set(            /* Pre-op modify function  */
        pb,
        SLAPI_PLUGIN_PRE_MODIFY_FN,
        (void *) testpreop_mod
    );
    rc |= slapi_pblock_set(            /* Pre-op modrdn function  */
        pb,
        SLAPI_PLUGIN_PRE_MODRDN_FN,
        (void *) testpreop_modrdn
    );
    rc |= slapi_pblock_set(            /* Pre-op delete function  */
        pb,
        SLAPI_PLUGIN_PRE_DELETE_FN,
        (void *) testpreop_del
    );
    rc |= slapi_pblock_set(            /* Pre-op abandon function */
        pb,
        SLAPI_PLUGIN_PRE_ABANDON_FN,
        (void *) testpreop_abandon
    );
    rc |= slapi_pblock_set(            /* Pre-op result function  */
        pb,
        SLAPI_PLUGIN_PRE_RESULT_FN,
        (void *) testpreop_send
    );
    rc |= slapi_pblock_set(            /* Pre-op referral function*/
        pb,
        SLAPI_PLUGIN_PRE_REFERRAL_FN,
        (void *) testpreop_send
    );
    rc |= slapi_pblock_set(            /* Pre-op entry send func. */
        pb,
        SLAPI_PLUGIN_PRE_ENTRY_FN,
        (void *) testpreop_send
    );
    return (rc);
}
