/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2021 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details.
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* rootdse.c - routines to manage the root DSE */

#include <stdio.h>
#include "slap.h"
#include "fe.h"

/* XXXmcs: why not look at the NO-USER-MODIFICATION flag instead? */
static char *readonly_attributes[] = {
    "namingcontexts",
    "nsBackendSuffix",
    "subschemasubentry",
    "supportedldapversion",
    "supportedcontrol",
    "supportedextension",
    "supportedfeatures",
    "supportedsaslmechanisms",
    "availablesaslmechanisms",
    "dataversion",
    "ref",
    "vendorName",
    "vendorVersion",
    ATTR_NETSCAPEMDSUFFIX,
    NULL};


static char *writable_attributes[] = {
    "copiedfrom",
    "copyingfrom",
    "aci",
    NULL};


/*
 * function: is_readonly_attr
 * args: attr - candidate attribute name
 * returns: 0 if this attribute may be written to the root DSE
 *          1 if it may not be written.
 * notes: should probably be integrated into syntax AVL tree, so that
 *        this list can be configured at runtime.
 */
static int
rootdse_is_readonly_attr(char *attr)
{
    int i;

    if (NULL == attr) {
        return 1; /* I guess.  It's not really an attribute at all */
    }

    /*
     * optimization: check for attributes we're likely to be writing
     * frequently.
     */
    for (i = 0; NULL != writable_attributes[i]; i++) {
        if (strncasecmp(attr, writable_attributes[i],
                        strlen(writable_attributes[i])) == 0) {
            return 0;
        }
    }

    for (i = 0; NULL != readonly_attributes[i]; i++) {
        if (strncasecmp(attr, readonly_attributes[i],
                        strlen(readonly_attributes[i])) == 0) {
            return 1;
        }
    }
    return 0;
}


/*
 * Handle a read operation on the root DSE (the entry with DN "");
 * Note: we're copying a lot of attributes here.  It might be better
 * to keep track of which we really need to free, and arrange that
 * the others are unlinked from the attribute list of the entry
 * before calling slapi_entry_free().
 */
int
read_root_dse(Slapi_PBlock *pb,
              Slapi_Entry *e,
              Slapi_Entry *entryAfter __attribute__((unused)),
              int *returncode,
              char *returntext __attribute__((unused)),
              void *arg __attribute__((unused)))
{
    int i;
    struct berval *vals[2];
    struct berval val;
    struct berval **bvals;
    Slapi_Backend *be;
    char *cookie = NULL;
    char **strs;
    void *node;
    Slapi_DN *sdn;

    /*
     * Check that we were doing a base search on the root dse.
     */
    {
        int scope;
        slapi_pblock_get(pb, SLAPI_SEARCH_SCOPE, &scope);
        if (scope != LDAP_SCOPE_BASE) {
            *returncode = LDAP_NO_SUCH_OBJECT;
            return SLAPI_DSE_CALLBACK_ERROR;
        }
    }

    vals[0] = &val;
    vals[1] = NULL;

    /* loop through backend suffixes to get namingcontexts attr */
    attrlist_delete(&e->e_attrs, "namingcontexts");

    sdn = slapi_get_first_suffix(&node, 0);
    while (sdn) {
        val.bv_val = (char *)slapi_sdn_get_dn(sdn); /* jcm: had to cast away const */
        val.bv_len = strlen(val.bv_val);
        attrlist_merge(&e->e_attrs, "namingcontexts", vals);
        sdn = slapi_get_next_suffix(&node, 0);
    }

    val.bv_val = config_get_default_naming_context();
    if (val.bv_val) {
        val.bv_len = strlen(val.bv_val);
        attrlist_replace(&e->e_attrs, "defaultnamingcontext", vals);
    }

    attrlist_delete(&e->e_attrs, "nsBackendSuffix");
    for (be = slapi_get_first_backend(&cookie); be != NULL;
         be = slapi_get_next_backend(cookie)) {

        char *base;
        char *be_name;
        const Slapi_DN *be_suffix;

        if (slapi_be_private(be))
            continue;

        /* tolerate a backend under construction containing no suffix */
        if ((be_suffix = slapi_be_getsuffix(be, 0)) == NULL)
            continue;

        if ((base = (char *)slapi_sdn_get_dn(be_suffix)) == NULL)
            continue;
        if ((be_name = slapi_be_get_name(be)) == NULL)
            continue;

        val.bv_len = strlen(base) + strlen(be_name) + 1;
        val.bv_val = slapi_ch_malloc(val.bv_len + 1);
        sprintf(val.bv_val, "%s:%s", be_name, base);
        attrlist_merge(&e->e_attrs, "nsBackendSuffix", vals);
        slapi_ch_free((void **)&val.bv_val);
    }
    slapi_ch_free((void **)&cookie);

    /* schema entry */
    val.bv_val = SLAPD_SCHEMA_DN;
    val.bv_len = sizeof(SLAPD_SCHEMA_DN) - 1;
    attrlist_replace(&e->e_attrs, "subschemasubentry", vals);

    /* supported extended operations */
    attrlist_delete(&e->e_attrs, "supportedExtension");
    if ((strs = slapi_get_supported_extended_ops_copy()) != NULL) {
        for (i = 0; strs[i] != NULL; ++i) {
            val.bv_val = strs[i];
            val.bv_len = strlen(strs[i]);
            attrlist_merge(&e->e_attrs, "supportedExtension", vals);
        }
        charray_free(strs);
    }

    /* supported controls */
    attrlist_delete(&e->e_attrs, "supportedControl");
    if (slapi_get_supported_controls_copy(&strs, NULL) == 0 && strs != NULL) {
        for (i = 0; strs[i] != NULL; ++i) {
            val.bv_val = strs[i];
            val.bv_len = strlen(strs[i]);
            attrlist_merge(&e->e_attrs, "supportedControl", vals);
        }
        charray_free(strs);
    }

    /* supported features */
    attrlist_delete(&e->e_attrs, "supportedFeatures");
    if (slapi_get_supported_features_copy(&strs) == 0 && strs != NULL) {
        for (i = 0; strs[i] != NULL; ++i) {
            val.bv_val = strs[i];
            val.bv_len = strlen(strs[i]);
            attrlist_merge(&e->e_attrs, "supportedFeatures", vals);
        }
        charray_free(strs);
    }

    /* supported/accepted sasl mechanisms */
    attrlist_delete(&e->e_attrs, "supportedSASLMechanisms");
    if ((strs = ids_sasl_listmech(pb, PR_FALSE)) != NULL) {
        for (i = 0; strs[i] != NULL; ++i) {
            val.bv_val = strs[i];
            val.bv_len = strlen(strs[i]);
            attrlist_merge(&e->e_attrs, "supportedSASLMechanisms", vals);
        }
        charray_free(strs);
    }

    /* available SASL mechs */
    attrlist_delete(&e->e_attrs, "availableSASLMechanisms");
    if ((strs = ids_sasl_listmech(pb, PR_TRUE)) != NULL) {
        for (i = 0; strs[i] != NULL; ++i) {
            val.bv_val = strs[i];
            val.bv_len = strlen(strs[i]);
            attrlist_merge(&e->e_attrs, "availableSASLMechanisms", vals);
        }
        charray_free(strs);
    }

    /* supported LDAP versions */
    val.bv_val = "2";
    val.bv_len = 1;
    attrlist_replace(&e->e_attrs, "supportedldapversion", vals);
    val.bv_val = "3";
    val.bv_len = 1;
    attrlist_merge(&e->e_attrs, "supportedldapversion", vals);

    /* superior references (ref attribute) */
    attrlist_delete(&e->e_attrs, "ref");
    if ((bvals = g_get_default_referral()) != NULL) {
        for (i = 0; bvals[i] != NULL; ++i) {
            val.bv_val = bvals[i]->bv_val;
            val.bv_len = bvals[i]->bv_len;
            attrlist_merge(&e->e_attrs, "ref", vals);
        }
    }

    /* RFC 3045 attributes: vendorName and vendorVersion */
    val.bv_val = SLAPD_VENDOR_NAME;
    val.bv_len = strlen(val.bv_val);
    attrlist_replace(&e->e_attrs, "vendorName", vals);
    val.bv_val = slapd_get_version_value();
    val.bv_len = strlen(val.bv_val);
    attrlist_replace(&e->e_attrs, "vendorVersion", vals);
    slapi_ch_free((void **)&val.bv_val);

    /* Server Data Version */
    if ((val.bv_val = (char *)get_server_dataversion()) != NULL) { /* jcm cast away const */
        val.bv_len = strlen(val.bv_val);
        attrlist_replace(&e->e_attrs, attr_dataversion, vals);
    }

    /* machine data suffix
     * this has been added in 4.0 for replication purpose
     * and since 5.0 is now unused by the core server,
     * however some functionalities of the console framework 5.01
     * still depend on this
     */
    if ((val.bv_val = get_config_DN()) != NULL) {
        val.bv_len = strlen(val.bv_val);
        attrlist_replace(&e->e_attrs, ATTR_NETSCAPEMDSUFFIX, vals);
    }

#ifdef notdef
    /* XXXggood testing - print the size of the changelog db */
    {
        unsigned int clsize;
        clsize = get_changelog_size();
        sprintf(buf, "%u", clsize);
        val.bv_val = buf;
        val.bv_len = strlen(buf);
        attrlist_replace(&e->e_attrs, "changelogsize", vals);
        slapi_ch_free((void **)&val.bv_val);
    }
#endif /* notdef */

    /* vlvsearch is list of dns to VLV Search Specifications */
    attrlist_delete(&e->e_attrs, "vlvsearch");
    cookie = NULL;
    be = slapi_get_first_backend(&cookie);
    while (be) {
        if (!be->be_private) {
            /* Generate searches to find the VLV Search Specifications */
            int r;
            Slapi_PBlock *resultpb = NULL;
            Slapi_Entry **entry = NULL;
            Slapi_DN dn;
            slapi_sdn_init(&dn);
            be_getconfigdn(be, &dn);
            resultpb = slapi_search_internal(slapi_sdn_get_ndn(&dn), LDAP_SCOPE_ONELEVEL, "objectclass=vlvsearch", NULL, NULL, 1);
            slapi_sdn_done(&dn);
            slapi_pblock_get(resultpb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entry);
            slapi_pblock_get(resultpb, SLAPI_PLUGIN_INTOP_RESULT, &r);
            if (r == LDAP_SUCCESS) {
                for (; *entry; ++entry) {
                    val.bv_val = slapi_entry_get_dn(*entry);
                    val.bv_len = strlen(val.bv_val);
                    attrlist_merge(&e->e_attrs, "vlvsearch", vals);
                }
            }
            slapi_free_search_results_internal(resultpb);
            slapi_pblock_destroy(resultpb);
        }

        be = slapi_get_next_backend(cookie);
    }
    slapi_ch_free((void **)&cookie);
    *returncode = LDAP_SUCCESS;
    return SLAPI_DSE_CALLBACK_OK;
}


/*
 * Handle a modification request on the root DSE.  Only certain
 * attributes are writable.  If an attempt to modify an attribute
 * which is not allowed, return LDAP_UNWILLING_TO_PERFORM, unless
 * the modification is disallowed because of ACL checking, in which
 * case LDAP_INSUFFICIENT_ACCESS is returned.
 */
int
modify_root_dse(Slapi_PBlock *pb,
                Slapi_Entry *entryBefore __attribute__((unused)),
                Slapi_Entry *e __attribute__((unused)),
                int *returncode,
                char *returntext,
                void *arg __attribute__((unused)))
{
    LDAPMod **mods;

    /*
     * Make a pass through the attributes and check them
     * to make sure none are computed.  Also check for
     * reflected attributes and reject those as well.
     * Eventually, some reflected attributes might be
     * writable, but for now, none are.
     */

    slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
    if (NULL != mods) {
        int i;
        for (i = 0; NULL != mods[i]; i++) {
            if (rootdse_is_readonly_attr(mods[i]->mod_type)) {
                /* The modification is disallowed */
                *returncode = LDAP_UNWILLING_TO_PERFORM;
                strcpy(returntext, "Modification of these root DSE attributes not allowed");
                return SLAPI_DSE_CALLBACK_ERROR;
            }
        }
    }

    *returncode = LDAP_SUCCESS;
    return SLAPI_DSE_CALLBACK_OK; /* success -- apply the changes */
}
