/* 
 * 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.
 */
/************************************************************

 testbind.c

 This source file provides an example of a pre-operation plug-in
 function that handles authentication.

 * test_bind   (called before an LDAP bind   operation)
   Handles simple authentication through the plug-in API.

 * test_search (called before an LDAP search operation)
   Logs the bind DN of the client requesting the search.

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

static Slapi_ComponentId * plugin_id = NULL;

/* Handle simple authentication through the plug-in API. */
int
test_bind(Slapi_PBlock * pb)
{
    char          *  dn;               /* Target DN                 */
    int              method;           /* Authentication method     */
    struct berval *  credentials;      /* Client SASL credentials   */
    Slapi_DN      *  sdn      = NULL;  /* DN used in internal srch  */
    char          *  attrs[2] = {      /* Look at userPassword only */
                                SLAPI_USERPWD_ATTR,
                                NULL
                     };
    Slapi_Entry   *  entry    = NULL;  /* Entry returned by srch    */
    Slapi_Attr    *  attr     = NULL;  /* Pwd attr in entry found   */
    int              is_repl;          /* Is this replication?      */
    int              is_intl;          /* Is this an internal op?   */
    int              connId, opId, rc = 0;
    long             msgId;
        
    /* Obtain the bind information from the parameter block.        */
    rc |= slapi_pblock_get(pb, SLAPI_BIND_TARGET,             &dn);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_METHOD,             &method);
    rc |= slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS,        &credentials);
    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);
    rc |= slapi_pblock_get(pb, SLAPI_IS_INTERNAL_OPERATION,   &is_intl);

    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_bind in test-bind plug-in",
            "Could not get parameters for bind operation (error %d).\n", rc
        );
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        return (LDAP_OPERATIONS_ERROR);/* Server aborts bind here.  */
    }

    /* The following code handles simple authentication, where a
     * user offers a bind DN and a password for authentication.
     *
     * Handling simple authentication is a matter of finding the
     * entry corresponding to the bind DN sent in the request,
     * then if the entry is found, checking whether the password
     * sent in the request matches a value found on the
     * userPassword attribute of the entry.                         */

    /* Avoid interfering with replication or internal operations.   */
    if (!is_repl && !is_intl) switch (method) {
    case LDAP_AUTH_SIMPLE:

        /* Find the entry specified by the bind DN...               */
        sdn = slapi_sdn_new_dn_byref(dn);
        rc |= slapi_search_internal_get_entry(
            sdn,
            attrs,
            &entry,
            plugin_id
            );
        slapi_sdn_free(&sdn);

        if (rc != 0 || entry == NULL) {
            slapi_log_info_ex(
                SLAPI_LOG_INFO_AREA_PLUGIN,
                SLAPI_LOG_INFO_LEVEL_DEFAULT,
                msgId,
                connId,
                opId,
                "test_bind in test-bind plug-in",
                "Could not find entry: %s\n", dn
            );
            rc = LDAP_NO_SUCH_OBJECT;
            slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
            return (rc);
        } else {
        /* ...check credentials against the userpassword...         */
            Slapi_Value    *  credval; /* Value of credentials      */
            Slapi_ValueSet * pwvalues; /* Password attribute values */
                        
            rc |= slapi_entry_attr_find(
                entry,
                SLAPI_USERPWD_ATTR,
                &attr
            );

            if (attr == NULL) {

                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    msgId,
                    connId,
                    opId,
                    "test_bind in test-bind plug-in",
                    "Entry %s has no userpassword.\n",
                    dn
                );
                rc = LDAP_INAPPROPRIATE_AUTH;
                slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
                return (rc);
            }

            rc |= slapi_attr_get_valueset(
                attr,
                &pwvalues
            );
            if (rc != 0 || slapi_valueset_count(pwvalues) == 0) {
                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    msgId,
                    connId,
                    opId,
                    "test_bind in test-bind plug-in",
                    "Entry %s has no %s attribute values.\n",
                    dn, SLAPI_USERPWD_ATTR
                );
                rc = LDAP_INAPPROPRIATE_AUTH;
                slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
                                slapi_valueset_free(pwvalues);
                                return (rc);
            }

            credval = slapi_value_new_berval(credentials);

            rc = slapi_pw_find_valueset(pwvalues, credval);

            slapi_value_free(&credval);
                        slapi_valueset_free(pwvalues);

            if (rc != 0) {
                slapi_log_info_ex(
                    SLAPI_LOG_INFO_AREA_PLUGIN,
                    SLAPI_LOG_INFO_LEVEL_DEFAULT,
                    msgId,
                    connId,
                    opId,
                    "test_bind in test-bind plug-in",
                    "Credentials are not correct.\n"
                );
                rc = LDAP_INVALID_CREDENTIALS;
                slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
                return (rc);
            }
        } 

        /* ...if successful, set authentication for the connection. */
        if (rc != 0) return (rc);
        rc |= slapi_pblock_set(pb, SLAPI_CONN_DN, slapi_ch_strdup(dn));
        rc |= slapi_pblock_set(pb, SLAPI_CONN_AUTHMETHOD, SLAPD_AUTH_SIMPLE);
        if (rc != 0) {
            slapi_log_info_ex(
                SLAPI_LOG_INFO_AREA_PLUGIN,
                SLAPI_LOG_INFO_LEVEL_DEFAULT,
                msgId,
                connId,
                opId,
                "test_bind in test-bind plug-in",
                "Failed to set connection info.\n"
            );
            rc = LDAP_OPERATIONS_ERROR;
            slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
            return (rc);
        } else {
            slapi_log_info_ex(
                SLAPI_LOG_INFO_AREA_PLUGIN,
                SLAPI_LOG_INFO_LEVEL_DEFAULT,
                msgId,
                connId,
                opId,
                "test_bind in test-bind plug-in",
                "Authenticated: %s\n", dn
            );

            /* Now that authentication succeeded, the plug-in
             * returns a value greater than 0, even though the
             * authentication has been successful. A return
             * code > 0 tells the server not to continue 
             * processing the bind. A return code of 0, such
             * as LDAP_SUCCESS tells the server to continue
             * processing the operation.                            */
            slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
            rc = 1;
        }
        break;

    /* This plug-in supports only simple authentication.            */
    case LDAP_AUTH_NONE:
        /* Anonymous binds are handled by the front-end before
         * pre-bind plug-in functions are called, so we should
         * never reach this part of the code.                       */
    case LDAP_AUTH_SASL:
    default:
        slapi_log_info_ex(
            SLAPI_LOG_INFO_AREA_PLUGIN,
            SLAPI_LOG_INFO_LEVEL_DEFAULT,
            msgId,
            connId,
            opId,
            "test_bind in test-bind plug-in",
            "Plug-in does not handle auth. method: %d\n", method
        );
        rc = 0;                        /* Let server handle bind.   */
        break;
    }

    return (rc);                       /* Server stops processing
                                        * the bind if rc > 0.       */
}

/* Log the bind DN of the client requesting the search.             */
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);
}

/* Register the plug-in with the server.                            */
#ifdef _WIN32
__declspec(dllexport)
#endif
int
testbind_init(Slapi_PBlock * pb)
{
    int rc = 0;                        /* 0 means success           */
    rc |= slapi_pblock_set(
        pb,
        SLAPI_PLUGIN_VERSION,
        SLAPI_PLUGIN_CURRENT_VERSION
    );
    rc |= slapi_pblock_set(            /* Plug-in description       */
        pb,
        SLAPI_PLUGIN_DESCRIPTION,
        (void *) &bindpdesc
    );
    rc |= slapi_pblock_set(            /* Pre-op bind function      */
        pb,
        SLAPI_PLUGIN_PRE_BIND_FN,
        (void *) test_bind
    );
    rc |= slapi_pblock_set(            /* Pre-op search function    */
        pb,
        SLAPI_PLUGIN_PRE_SEARCH_FN,
        (void *) test_search
    );
    /* Get plug-in identity to pass to internal operations.         */
    rc |= slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id);
    return (rc);
}
