/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2005 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


#include <ldap.h>

#include "acl.h"

/* safer than doing strcat unprotected */
/* news2 is optional, provided as a convenience */
/* capacity is the capacity of the gerstr, size is the current length */
static void
_append_gerstr(
    char **gerstr,
    size_t *capacity,
    size_t *size,
    const char *news,
    const char *news2)
{
    size_t len;
    size_t increment = 128;
    size_t fornull;

    if (!news) {
        return;
    }

    /* find out how much space we need */
    len = strlen(news);
    fornull = 1;
    if (news2) {
        len += strlen(news2);
        fornull++;
    }

    /* increase space if needed */
    while ((*size + len + fornull) > *capacity) {
        if ((len + fornull) > increment) {
            *capacity += len + fornull; /* just go ahead and grow the string enough */
        } else {
            *capacity += increment; /* rather than having lots of small increments */
        }
    }

    if (!*gerstr) {
        *gerstr = slapi_ch_malloc(*capacity);
        **gerstr = 0;
    } else {
        *gerstr = slapi_ch_realloc(*gerstr, *capacity);
    }
    strcat(*gerstr, news);
    if (news2) {
        strcat(*gerstr, news2);
    }

    *size += len;

    return;
}

static int
_ger_g_permission_granted(
    Slapi_PBlock *pb,
    Slapi_Entry *e,
    const char *subjectdn,
    char **errbuf)
{
    char *proxydn = NULL;
    Slapi_DN *requestor_sdn, *entry_sdn;
    char *errtext = NULL;
    int isroot;
    int rc;

    /*
     * Theorically, we should check if the entry has "g"
     * permission granted to the requestor. If granted,
     * allows the effective rights on that entry and its
     * attributes within the entry to be returned for
     * ANY subject.
     *
     * "G" permission granting has not been implemented yet,
     * the current release assumes that "g" permission be
     * granted to root and owner of any entry.
     */

    /*
     * The requestor may be either the bind dn or a proxy dn
     */
    if ((proxyauth_get_dn(pb, &proxydn, &errtext) == LDAP_SUCCESS) && (proxydn != NULL)) {
        requestor_sdn = slapi_sdn_new_dn_passin(proxydn);
    } else {
        slapi_ch_free_string(&proxydn); /* this could still have been set - free it */
        Operation *pb_op;
        slapi_pblock_get(pb, SLAPI_OPERATION, &pb_op);
        requestor_sdn = &(pb_op->o_sdn);
    }
    if (slapi_sdn_get_dn(requestor_sdn) == NULL) {
        slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                      "_ger_g_permission_granted - Anonymous has no g permission\n");
        rc = LDAP_INSUFFICIENT_ACCESS;
        goto bailout;
    }
    isroot = slapi_dn_isroot(slapi_sdn_get_dn(requestor_sdn));
    if (isroot) {
        /* Root has "g" permission on any entry */
        rc = LDAP_SUCCESS;
        goto bailout;
    }

    entry_sdn = slapi_entry_get_sdn(e);
    if (entry_sdn == NULL || slapi_sdn_get_dn(entry_sdn) == NULL) {
        rc = LDAP_SUCCESS;
        goto bailout;
    }

    if (slapi_sdn_compare(requestor_sdn, entry_sdn) == 0) {
        /* Owner has "g" permission on his own entry */
        rc = LDAP_SUCCESS;
        goto bailout;
    }

    /* if the requestor and the subject user are identical, let's grant it */
    if (strcasecmp(slapi_sdn_get_ndn(requestor_sdn), subjectdn) == 0) {
        /* Requestor should see his own permission rights on any entry */
        rc = LDAP_SUCCESS;
        goto bailout;
    }

    aclutil_str_append(errbuf, "get-effective-rights: requestor has no g permission on the entry");
    slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                  "_ger_g_permission_granted - %s\n", *errbuf);
    rc = LDAP_INSUFFICIENT_ACCESS;

bailout:
    if (proxydn) {
        /* The ownership of proxydn has passed to requestor_sdn */
        slapi_sdn_free(&requestor_sdn);
    }
    return rc;
}

static int
_ger_parse_control(
    Slapi_PBlock *pb,
    char **subjectndn,
    int *iscritical,
    char **errbuf)
{
    LDAPControl **requestcontrols;
    struct berval *subjectber;
    BerElement *ber;
    size_t subjectndnlen = 0;
    char *orig = NULL;
    char *normed = NULL;

    if (NULL == subjectndn) {
        return LDAP_OPERATIONS_ERROR;
    }

    *subjectndn = NULL;

    /*
     * Get the control
     */
    slapi_pblock_get(pb, SLAPI_REQCONTROLS, (void *)&requestcontrols);
    slapi_control_present(requestcontrols,
                          LDAP_CONTROL_GET_EFFECTIVE_RIGHTS,
                          &subjectber,
                          iscritical);
    if (subjectber == NULL || subjectber->bv_val == NULL ||
        subjectber->bv_len == 0) {
        aclutil_str_append(errbuf, "get-effective-rights: missing subject");
        slapi_log_err(SLAPI_LOG_ERR, plugin_name, "_ger_parse_control - %s\n", *errbuf);
        if (iscritical)
            return LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* RFC 4511 4.1.11 */
        else
            return LDAP_INVALID_SYNTAX;
    }

    if (strncasecmp("dn:", subjectber->bv_val, 3) == 0) {
        /*
         * This is a non-standard support to allow the subject being a plain
         * or base64 encoding string. Hence users using -J option in
         * ldapsearch don't have to do BER encoding for the subject.
         */
        orig = slapi_ch_malloc(subjectber->bv_len + 1);
        strncpy(orig, subjectber->bv_val, subjectber->bv_len);
        *(orig + subjectber->bv_len) = '\0';
    } else {
        ber = ber_init(subjectber);
        if (ber == NULL) {
            aclutil_str_append(errbuf, "get-effective-rights: ber_init failed for the subject");
            slapi_log_err(SLAPI_LOG_ERR, plugin_name, "_ger_parse_control - %s\n", *errbuf);
            if (iscritical)
                return LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* RFC 4511 4.1.11 */
            else
                return LDAP_OPERATIONS_ERROR;
        }
        /* "a" means to allocate storage as needed for octet string */
        if (ber_scanf(ber, "a", &orig) == LBER_ERROR) {
            aclutil_str_append(errbuf, "get-effective-rights: invalid ber tag in the subject");
            slapi_log_err(SLAPI_LOG_ERR, plugin_name, "_ger_parse_control - %s\n", *errbuf);
            ber_free(ber, 1);
            if (iscritical)
                return LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* RFC 4511 4.1.11 */
            else
                return LDAP_INVALID_SYNTAX;
        }
        ber_free(ber, 1);
    }

    /*
     * The current implementation limits the subject to authorization ID
     * (see section 9 of RFC 2829) only. It also only supports the "dnAuthzId"
     * flavor, which looks like "dn:<DN>" where null <DN> is for anonymous.
     */
    subjectndnlen = orig ? strlen(orig) : 0;
    if (NULL == orig || subjectndnlen < 3 || strncasecmp("dn:", orig, 3) != 0) {
        aclutil_str_append(errbuf, "get-effective-rights: subject is not dnAuthzId");
        slapi_log_err(SLAPI_LOG_ERR, plugin_name, "_ger_parse_control - %s\n", *errbuf);
        slapi_ch_free_string(&orig);
        if (iscritical)
            return LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* RFC 4511 4.1.11 */
        else
            return LDAP_INVALID_SYNTAX;
    }

    /* memmove is safe for overlapping copy */
    normed = slapi_create_dn_string("%s", orig + 3);
    if (NULL == normed) {
        aclutil_str_append(errbuf, "get-effective-rights: failed to normalize dn: ");
        aclutil_str_append(errbuf, orig);
        slapi_log_err(SLAPI_LOG_ERR, plugin_name, "_ger_parse_control - %s\n", *errbuf);
        slapi_ch_free_string(&orig);
        if (iscritical)
            return LDAP_UNAVAILABLE_CRITICAL_EXTENSION; /* RFC 4511 4.1.11 */
        else
            return LDAP_INVALID_SYNTAX;
    }
    slapi_ch_free_string(&orig);
    *subjectndn = normed;
    slapi_dn_ignore_case(*subjectndn);
    return LDAP_SUCCESS;
}

static void
_ger_swap_aclcb(struct acl_cblock *cb_1, struct acl_cblock *cb_2)
{
    short swap_aclcb_aclsignature;
    short swap_aclcb_state;
    Slapi_DN *swap_aclcb_sdn;
    aclEvalContext swap_aclcb_eval_context;

    /* swap the data of two acl cblocks
     * while holding th elock of one of them. This allows
     * to temporarily use different content in the cblock
     * without freeing data that might be accessed by another thread
     * Since the lock in one cblock is used to give exclusive access
     * the locks will not be swapped
     */
    swap_aclcb_aclsignature = cb_1->aclcb_aclsignature;
    swap_aclcb_state = cb_1->aclcb_state;
    swap_aclcb_sdn = cb_1->aclcb_sdn;
    swap_aclcb_eval_context = cb_1->aclcb_eval_context;

    cb_1->aclcb_aclsignature = cb_2->aclcb_aclsignature;
    cb_1->aclcb_state = cb_2->aclcb_state;
    cb_1->aclcb_sdn = cb_2->aclcb_sdn;
    cb_1->aclcb_eval_context = cb_2->aclcb_eval_context;


    cb_2->aclcb_aclsignature = swap_aclcb_aclsignature;
    cb_2->aclcb_state = swap_aclcb_state;
    cb_2->aclcb_sdn = swap_aclcb_sdn;
    cb_2->aclcb_eval_context = swap_aclcb_eval_context;

}

static void
_ger_release_gerpb(
    Slapi_PBlock **gerpb,
    void **aclcb,    /* original aclcb */
    Slapi_PBlock *pb /* original pb */
    )
{
    if (*gerpb) {
        slapi_pblock_destroy(*gerpb);
        *gerpb = NULL;
    }

    /* Put the original aclcb back to pb */
    if (*aclcb) {
        Connection *conn = NULL;
        slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
        if (conn) {
            struct acl_cblock *geraclcb;
            geraclcb = (struct acl_cblock *)acl_get_ext(ACL_EXT_CONNECTION, conn);
            PR_Lock(geraclcb->aclcb_lock);
            _ger_swap_aclcb(geraclcb, (struct acl_cblock *)*aclcb);
            acl_conn_ext_destructor((struct acl_cblock *)*aclcb, NULL, NULL);
            PR_Unlock(geraclcb->aclcb_lock);
            *aclcb = NULL;
        }
    }
}

static int
_ger_new_gerpb(
    Slapi_PBlock *pb,
    Slapi_Entry *e __attribute__((unused)),
    const char *subjectndn,
    Slapi_PBlock **gerpb,
    void **save_aclcb, /* original aclcb */
    char **errbuf __attribute__((unused)))
{
    Connection *conn;
    struct acl_cblock *ger_aclcb;
    struct acl_cblock *conn_aclcb;
    Acl_PBlock *geraclpb;
    Operation *gerop;
    int rc = LDAP_SUCCESS;

    *save_aclcb = NULL;
    *gerpb = slapi_pblock_new();
    if (*gerpb == NULL) {
        rc = LDAP_NO_MEMORY;
        goto bailout;
    }

    {
        /* aclpb initialization needs the backend */
        Slapi_Backend *be;
        slapi_pblock_get(pb, SLAPI_BACKEND, &be);
        slapi_pblock_set(*gerpb, SLAPI_BACKEND, be);
    }

    {
        int isroot = slapi_dn_isroot(subjectndn);
        slapi_pblock_set(*gerpb, SLAPI_REQUESTOR_ISROOT, &isroot);
    }

    /* Save requestor's aclcb and set subjectdn's one */
    {
        slapi_pblock_get(pb, SLAPI_CONNECTION, &conn);
        slapi_pblock_set(*gerpb, SLAPI_CONNECTION, conn);

        /* Can't share the conn->aclcb because of different context */
        ger_aclcb = (struct acl_cblock *)acl_conn_ext_constructor(NULL, NULL);
        if (ger_aclcb == NULL) {
            rc = LDAP_NO_MEMORY;
            goto bailout;
        }
        slapi_sdn_set_ndn_byval(ger_aclcb->aclcb_sdn, subjectndn);
        conn_aclcb = acl_get_ext(ACL_EXT_CONNECTION, conn);
        PR_Lock(conn_aclcb->aclcb_lock);
        _ger_swap_aclcb(conn_aclcb, ger_aclcb);
        *save_aclcb = ger_aclcb;
        PR_Unlock(conn_aclcb->aclcb_lock);
    }

    {
        gerop = operation_new(OP_FLAG_INTERNAL);
        if (gerop == NULL) {
            rc = LDAP_NO_MEMORY;
            goto bailout;
        }
        /*
         * conn is a no-use parameter in the functions
         * chained down from factory_create_extension
         */
        gerop->o_extension = factory_create_extension(get_operation_object_type(), (void *)gerop, (void *)conn);
        slapi_pblock_set(*gerpb, SLAPI_OPERATION, gerop);
        slapi_sdn_set_ndn_byval(&gerop->o_sdn, subjectndn);
        geraclpb = acl_get_ext(ACL_EXT_OPERATION, (void *)gerop);
        acl_init_aclpb(*gerpb, geraclpb, subjectndn, 0);
        geraclpb->aclpb_res_type |= ACLPB_EFFECTIVE_RIGHTS;
    }


bailout:
    if (rc != LDAP_SUCCESS) {
        _ger_release_gerpb(gerpb, save_aclcb, pb);
    }

    return rc;
}

/*
 * Callers should have already allocated *gerstr to hold at least
 * "entryLevelRights: adnvxxx\n".
 */
unsigned long
_ger_get_entry_rights(
    Slapi_PBlock *gerpb,
    Slapi_Entry *e,
    const char *subjectndn __attribute__((unused)),
    char **gerstr,
    size_t *gerstrsize,
    size_t *gerstrcap,
    char **errbuf __attribute__((unused)))
{
    unsigned long entryrights = 0;
    Slapi_RDN *rdn = NULL;
    char *rdntype = NULL;
    char *rdnvalue = NULL;

    _append_gerstr(gerstr, gerstrsize, gerstrcap, "entryLevelRights: ", NULL);

    slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                  "_ger_get_entry_rights - SLAPI_ACL_READ\n");
    if (acl_access_allowed(gerpb, e, "*", NULL, SLAPI_ACL_READ) == LDAP_SUCCESS) {
        /* v - view e */
        entryrights |= SLAPI_ACL_READ;
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "v", NULL);
    }
    slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                  "_ger_get_entry_rights - SLAPI_ACL_ADD\n");
    if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_ADD) == LDAP_SUCCESS) {
        /* a - add child entry below e */
        entryrights |= SLAPI_ACL_ADD;
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "a", NULL);
    }
    slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                  "_ger_get_entry_rights - SLAPI_ACL_DELETE\n");
    if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_DELETE) == LDAP_SUCCESS) {
        /* d - delete e */
        entryrights |= SLAPI_ACL_DELETE;
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "d", NULL);
    }

    if (config_get_moddn_aci()) {
        /* The server enforces the new MODDN aci right.
         * So the status 'n' is set if this right is granted.
         * Opposed to the legacy mode where this flag is set if
         * WRITE was granted on rdn attrbibute
         */
        if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_MODDN) == LDAP_SUCCESS) {
            slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                          "_ger_get_entry_rights - SLAPI_ACL_MODDN %s\n", slapi_entry_get_ndn(e));
            /* n - rename e */
            entryrights |= SLAPI_ACL_MODDN;
            _append_gerstr(gerstr, gerstrsize, gerstrcap, "n", NULL);
        }
    } else {
        /*
         * Some limitation/simplification applied here:
         * - The modrdn right requires the rights to delete the old rdn and
         *   the new one. However we have no knowledge of what the new rdn
         *   is going to be.
         * - In multi-valued RDN case, we check the right on
         *   the first rdn type only for now.
         */
        rdn = slapi_rdn_new_dn(slapi_entry_get_ndn(e));
        slapi_rdn_get_first(rdn, &rdntype, &rdnvalue);
        if (NULL != rdntype) {
            slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                          "_ger_get_entry_rights - SLAPI_ACL_WRITE_DEL & _ADD %s\n", rdntype);
            if (acl_access_allowed(gerpb, e, rdntype, NULL,
                                   ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS &&
                acl_access_allowed(gerpb, e, rdntype, NULL,
                                   ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS) {
                /* n - rename e */
                entryrights |= SLAPI_ACL_WRITE;
                _append_gerstr(gerstr, gerstrsize, gerstrcap, "n", NULL);
            }
        }
        slapi_rdn_free(&rdn);
    }
    if (entryrights == 0) {
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "none", NULL);
    }

    _append_gerstr(gerstr, gerstrsize, gerstrcap, "\n", NULL);

    return entryrights;
}

/*
 * *gerstr should point to a heap buffer since it may need
 * to expand dynamically.
 */
unsigned long
_ger_get_attr_rights(
    Slapi_PBlock *gerpb,
    Slapi_Entry *e,
    const char *subjectndn,
    char *type,
    char **gerstr,
    size_t *gerstrsize,
    size_t *gerstrcap,
    int isfirstattr,
    char **errbuf)
{
    unsigned long attrrights = 0;

    if (!isfirstattr) {
        _append_gerstr(gerstr, gerstrsize, gerstrcap, ", ", NULL);
    }
    _append_gerstr(gerstr, gerstrsize, gerstrcap, type, ":");

    slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                  "_ger_get_attr_rights - SLAPI_ACL_READ %s\n", type);
    if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_READ) == LDAP_SUCCESS) {
        /* r - read the values of type */
        attrrights |= SLAPI_ACL_READ;
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "r", NULL);
    }
    slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                  "_ger_get_attr_rights -  SLAPI_ACL_SEARCH %s\n", type);
    if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_SEARCH) == LDAP_SUCCESS) {
        /* s - search the values of type */
        attrrights |= SLAPI_ACL_SEARCH;
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "s", NULL);
    }
    slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                  "_ger_get_attr_rights - SLAPI_ACL_COMPARE %s\n", type);
    if (acl_access_allowed(gerpb, e, type, NULL, SLAPI_ACL_COMPARE) == LDAP_SUCCESS) {
        /* c - compare the values of type */
        attrrights |= SLAPI_ACL_COMPARE;
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "c", NULL);
    }
    slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                  "_ger_get_attr_rights - SLAPI_ACL_WRITE_ADD %s\n", type);
    if (acl_access_allowed(gerpb, e, type, NULL, ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS) {
        /* w - add the values of type */
        attrrights |= ACLPB_SLAPI_ACL_WRITE_ADD;
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "w", NULL);
    }
    slapi_log_err(SLAPI_LOG_ACL, plugin_name,
                  "_ger_get_attr_rights - SLAPI_ACL_WRITE_DEL %s\n", type);
    if (acl_access_allowed(gerpb, e, type, NULL, ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS) {
        /* o - delete the values of type */
        attrrights |= ACLPB_SLAPI_ACL_WRITE_DEL;
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "o", NULL);
    }
    /* If subjectdn has no general write right, check for self write */
    if (0 == (attrrights & (ACLPB_SLAPI_ACL_WRITE_DEL | ACLPB_SLAPI_ACL_WRITE_ADD))) {
        struct berval val;

        val.bv_val = (char *)subjectndn;
        val.bv_len = strlen(subjectndn);

        if (acl_access_allowed(gerpb, e, type, &val, ACLPB_SLAPI_ACL_WRITE_ADD) == LDAP_SUCCESS) {
            /* W - add self to the attribute */
            attrrights |= ACLPB_SLAPI_ACL_WRITE_ADD;
            _append_gerstr(gerstr, gerstrsize, gerstrcap, "W", NULL);
        }
        if (acl_access_allowed(gerpb, e, type, &val, ACLPB_SLAPI_ACL_WRITE_DEL) == LDAP_SUCCESS) {
            /* O - delete self from the attribute */
            attrrights |= ACLPB_SLAPI_ACL_WRITE_DEL;
            _append_gerstr(gerstr, gerstrsize, gerstrcap, "O", NULL);
        }
    }

    if (attrrights == 0) {
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "none", NULL);
    }

    return attrrights;
}

#define GER_GET_ATTR_RIGHTS(attrs)                                                \
    for (thisattr = (attrs); thisattr && *thisattr; thisattr++) {                 \
        _ger_get_attr_rights(gerpb, e, subjectndn, *thisattr,                     \
                             gerstr, gerstrsize, gerstrcap, isfirstattr, errbuf); \
        isfirstattr = 0;                                                          \
    }

#define GER_GET_ATTR_RIGHTA_EXT(c, inattrs, exattrs)                                  \
    ;                                                                                 \
    for (i = 0; attrs[i]; i++) {                                                      \
        if ((c) != *attrs[i] && charray_inlist((inattrs), attrs[i]) &&                \
            !charray_inlist((exattrs), attrs[i])) {                                   \
            _ger_get_attr_rights(gerpb, e, subjectndn, attrs[i],                      \
                                 gerstr, gerstrsize, gerstrcap, isfirstattr, errbuf); \
            isfirstattr = 0;                                                          \
        }                                                                             \
    }

void
_ger_get_attrs_rights(
    Slapi_PBlock *gerpb,
    Slapi_Entry *e,
    const char *subjectndn,
    char **attrs,
    char **gerstr,
    size_t *gerstrsize,
    size_t *gerstrcap,
    char **errbuf)
{
    int isfirstattr = 1;

    /* gerstr was initially allocated with enough space for one more line */
    _append_gerstr(gerstr, gerstrsize, gerstrcap, "attributeLevelRights: ", NULL);

    /*
     * If it's stated attribute list is given,
     * the first attr in the list should not be empty.
     * Otherwise, it's considered the list is not given.
     */
    if (attrs && *attrs && (strlen(*attrs) > 0)) {
        int i = 0;
        char **allattrs = NULL;
        char **opattrs = NULL;
        char **noexpattrs = NULL; /* attrs not to expose */
        char **myattrs = NULL;
        char **thisattr = NULL;
        int hasstar = charray_inlist(attrs, "*");
        int hasplus = charray_inlist(attrs, "+");
        Slapi_Attr *objclasses = NULL;
        Slapi_ValueSet *objclassvals = NULL;
        int isextensibleobj = 0;

        /* get all attrs available for the entry */
        slapi_entry_attr_find(e, "objectclass", &objclasses);
        if (NULL != objclasses) {
            Slapi_Value *v;
            slapi_attr_get_valueset(objclasses, &objclassvals);
            i = slapi_valueset_first_value(objclassvals, &v);
            if (-1 != i) {
                const char *ocname = NULL;
                allattrs = slapi_schema_list_objectclass_attributes(
                    (const char *)v->bv.bv_val,
                    SLAPI_OC_FLAG_REQUIRED | SLAPI_OC_FLAG_ALLOWED);
                /* check if this entry is an extensble object or not */
                ocname = slapi_value_get_string(v);
                if (strcasecmp(ocname, "extensibleobject") == 0) {
                    isextensibleobj = 1;
                }
                /* add "aci" to the allattrs to adjust to do_search */
                charray_add(&allattrs, slapi_attr_syntax_normalize("aci"));
                while (-1 != i) {
                    i = slapi_valueset_next_value(objclassvals, i, &v);
                    if (-1 != i) {
                        myattrs = slapi_schema_list_objectclass_attributes(
                            (const char *)v->bv.bv_val,
                            SLAPI_OC_FLAG_REQUIRED | SLAPI_OC_FLAG_ALLOWED);
                        /* check if this entry is an extensble object or not */
                        ocname = slapi_value_get_string(v);
                        if (strcasecmp(ocname, "extensibleobject") == 0) {
                            isextensibleobj = 1;
                        }
                        charray_merge_nodup(&allattrs, myattrs, 1 /*copy_strs*/);
                        charray_free(myattrs);
                    }
                }
            }
            slapi_valueset_free(objclassvals);
        }

        /* get operational attrs */
        opattrs = slapi_schema_list_attribute_names(SLAPI_ATTR_FLAG_OPATTR);
        noexpattrs = slapi_schema_list_attribute_names(SLAPI_ATTR_FLAG_NOEXPOSE);
        /* subtract no expose attrs from opattrs (e.g., unhashed pw) */
        charray_subtract(opattrs, noexpattrs, NULL);

        if (isextensibleobj) {
            for (i = 0; attrs[i]; i++) {
                if ('\0' == *attrs[i]) {
                    continue; /* skip an empty attr */
                }
                _ger_get_attr_rights(gerpb, e, subjectndn, attrs[i], gerstr,
                                     gerstrsize, gerstrcap, isfirstattr, errbuf);
                isfirstattr = 0;
            }
        } else {
            if (hasstar && hasplus) {
                GER_GET_ATTR_RIGHTS(allattrs);
                GER_GET_ATTR_RIGHTS(opattrs);
            } else if (hasstar) {
                GER_GET_ATTR_RIGHTS(allattrs);
                GER_GET_ATTR_RIGHTA_EXT('*', opattrs, allattrs);
            } else if (hasplus) {
                GER_GET_ATTR_RIGHTS(opattrs);
                GER_GET_ATTR_RIGHTA_EXT('+', allattrs, opattrs);
            } else {
                for (i = 0; attrs[i]; i++) {
                    if ('\0' == *attrs[i]) {
                        continue; /* skip an empty attr */
                    }
                    if (charray_inlist(noexpattrs, attrs[i])) {
                        continue;
                    } else if (charray_inlist(allattrs, attrs[i]) ||
                               charray_inlist(opattrs, attrs[i]) ||
                               (0 == strcasecmp(attrs[i], "dn")) ||
                               (0 == strcasecmp(attrs[i], "distinguishedName"))) {
                        _ger_get_attr_rights(gerpb, e, subjectndn, attrs[i],
                                             gerstr, gerstrsize, gerstrcap, isfirstattr, errbuf);
                        isfirstattr = 0;
                    } else {
                        /* if the attr does not belong to the entry,
                           "<attr>:none" is returned */
                        if (!isfirstattr) {
                            _append_gerstr(gerstr, gerstrsize, gerstrcap, ", ", NULL);
                        }
                        _append_gerstr(gerstr, gerstrsize, gerstrcap, attrs[i], ":");
                        _append_gerstr(gerstr, gerstrsize, gerstrcap, "none", NULL);
                        isfirstattr = 0;
                    }
                }
            }
        }
        charray_free(allattrs);
        charray_free(opattrs);
    } else {
        Slapi_Attr *prevattr = NULL, *attr;
        char *type;

        while (slapi_entry_next_attr(e, prevattr, &attr) == 0) {
            if (!slapi_attr_flag_is_set(attr, SLAPI_ATTR_FLAG_OPATTR)) {
                slapi_attr_get_type(attr, &type);
                _ger_get_attr_rights(gerpb, e, subjectndn, type, gerstr,
                                     gerstrsize, gerstrcap, isfirstattr, errbuf);
                isfirstattr = 0;
            }
            prevattr = attr;
        }
    }

    if (isfirstattr) {
        /* not a single attribute was retrived or specified */
        _append_gerstr(gerstr, gerstrsize, gerstrcap, "*:none", NULL);
    }
    return;
}

/*
 * controlType = LDAP_CONTROL_GET_EFFECTIVE_RIGHTS;
 * criticality = n/a;
 * controlValue = OCTET STRING of BER encoding of the SEQUENCE of
 *                  ENUMERATED LDAP code
 */
void
_ger_set_response_control(
    Slapi_PBlock *pb,
    int iscritical,
    int rc)
{
    LDAPControl **resultctrls = NULL;
    LDAPControl gerrespctrl;
    BerElement *ber = NULL;
    struct berval *berval = NULL;
    int found = 0;
    int i;

    if ((ber = der_alloc()) == NULL) {
        goto bailout;
    }

    /* begin sequence, enumeration, end sequence */
    ber_printf(ber, "{e}", rc);
    if (ber_flatten(ber, &berval) != LDAP_SUCCESS) {
        goto bailout;
    }
    gerrespctrl.ldctl_oid = LDAP_CONTROL_GET_EFFECTIVE_RIGHTS;
    gerrespctrl.ldctl_iscritical = iscritical;
    gerrespctrl.ldctl_value.bv_val = berval->bv_val;
    gerrespctrl.ldctl_value.bv_len = berval->bv_len;

    slapi_pblock_get(pb, SLAPI_RESCONTROLS, &resultctrls);
    for (i = 0; resultctrls && resultctrls[i]; i++) {
        if (strcmp(resultctrls[i]->ldctl_oid, LDAP_CONTROL_GET_EFFECTIVE_RIGHTS) == 0) {
            /*
             * We get here if search returns more than one entry
             * and this is not the first entry.
             */
            ldap_control_free(resultctrls[i]);
            resultctrls[i] = slapi_dup_control(&gerrespctrl);
            found = 1;
            break;
        }
    }

    if (!found) {
        /* slapi_pblock_set() will dup the control */
        slapi_pblock_set(pb, SLAPI_ADD_RESCONTROL, &gerrespctrl);
    }

bailout:
    ber_free(ber, 1);   /* ber_free() checks for NULL param */
    ber_bvfree(berval); /* ber_bvfree() checks for NULL param */
}

int
_ger_generate_template_entry(
    Slapi_PBlock *pb)
{
    Slapi_Entry *e = NULL;
    char **gerattrs = NULL;
    char **attrs = NULL;
    char **allowedattrs = NULL;
    char *templateentry = NULL;
    char *object = NULL;
    char *superior = NULL;
    char *p = NULL;
    const char *dn = NULL;
    Slapi_DN *sdn = NULL;
    char *dntype = NULL;
    int siz = 0;
    int len = 0;
    int i = 0;
    int notfirst = 0;
    int rc = LDAP_SUCCESS;

    slapi_pblock_get(pb, SLAPI_SEARCH_GERATTRS, &gerattrs);
    if (NULL == gerattrs) {
        slapi_log_err(SLAPI_LOG_ERR, plugin_name,
                      "_ger_generate_template_entry - Objectclass info is expected "
                      "in the attr list, e.g., \"*@person\"\n");
        rc = LDAP_SUCCESS;
        goto bailout;
    }
    for (i = 0; gerattrs && gerattrs[i]; i++) {
        object = strchr(gerattrs[i], '@');
        if (NULL != object && '\0' != *(++object)) {
            break;
        }
    }
    if (NULL == object) {
        rc = LDAP_SUCCESS; /* no objectclass info; ok to return */
        goto bailout;
    }
    /*
     * Either @objectclass or @objectclass:dntype is accepted.
     * If @objectclass, the first MUST attributetype (or the first MAY
     * attributetype if MUST does not exist) is used for the attribute
     * type in the leaf RDN.
     * If @objectclass:dntype, dntype is used for the attribute type in the
     * leaf RDN.
     */
    dntype = strchr(object, ':');
    if (dntype) { /* @objectclasse:dntype */
        *dntype++ = '\0';
    }

    attrs = slapi_schema_list_objectclass_attributes(
        (const char *)object, SLAPI_OC_FLAG_REQUIRED);
    allowedattrs = slapi_schema_list_objectclass_attributes(
        (const char *)object, SLAPI_OC_FLAG_ALLOWED);
    charray_merge(&attrs, allowedattrs, 0 /* no copy */);
    slapi_ch_free((void **)&allowedattrs); /* free just allowedattrs */
    if (NULL == attrs) {
        rc = LDAP_SUCCESS; /* bogus objectclass info; ok to return */
        goto bailout;
    }
    for (i = 0; attrs[i]; i++) {
        if (0 == strcasecmp(attrs[i], "objectclass")) {
            /* <*attrp>: <object>\n\0 */
            siz += strlen(attrs[i]) + 4 + strlen(object);
        } else {
            /* <*attrp>: (template_attribute)\n\0 */
            siz += strlen(attrs[i]) + 4 + 20;
        }
    }
    /* get the target dn where the template entry is located */
    slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
    dn = slapi_sdn_get_dn(sdn);
    if (dn) {
        /* dn: <attr>=<template_name>,<dn>\n\0 */
        if (dntype) {
            siz += strlen(dntype) + 30 + strlen(object) + strlen(dn);
        } else {
            siz += strlen(attrs[0] ? attrs[0] : "") + 30 + strlen(object) + strlen(dn);
        }
    } else {
        /* dn: <attr>=<template_name>\n\0 */
        if (dntype) {
            siz += strlen(dntype) + 30 + strlen(object);
        } else {
            siz += strlen(attrs[0] ? attrs[0] : "") + 30 + strlen(object);
        }
    }
    templateentry = (char *)slapi_ch_malloc(siz);
    if (NULL != dn && strlen(dn) > 0) {
        PR_snprintf(templateentry, siz,
                    "dn: %s=template_%s_objectclass,%s\n",
                    dntype ? dntype : attrs[0], object, dn);
    } else {
        PR_snprintf(templateentry, siz,
                    "dn: %s=template_%s_objectclass\n",
                    dntype ? dntype : attrs[0], object);
    }
    for (--i; i >= 0; i--) {
        len = strlen(templateentry);
        p = templateentry + len;
        if (0 == strcasecmp(attrs[i], "objectclass")) {
            PR_snprintf(p, siz - len, "%s: %s\n", attrs[i], object);
        } else {
            PR_snprintf(p, siz - len, "%s: (template_attribute)\n", attrs[i]);
        }
    }
    charray_free(attrs);

    while ((superior = slapi_schema_get_superior_name(object)) &&
           (0 != strcasecmp(superior, "top"))) {
        if (notfirst) {
            slapi_ch_free_string(&object);
        }
        notfirst = 1;
        object = superior;
        attrs = slapi_schema_list_objectclass_attributes(
            (const char *)superior, SLAPI_OC_FLAG_REQUIRED);
        for (i = 0; attrs && attrs[i]; i++) {
            if (0 == strcasecmp(attrs[i], "objectclass")) {
                /* <*attrp>: <object>\n\0 */
                siz += strlen(attrs[i]) + 4 + strlen(object);
            }
        }
        templateentry = (char *)slapi_ch_realloc(templateentry, siz);
        for (--i; i >= 0; i--) {
            len = strlen(templateentry);
            p = templateentry + len;
            if (0 == strcasecmp(attrs[i], "objectclass")) {
                PR_snprintf(p, siz - len, "%s: %s\n", attrs[i], object);
            }
        }
        charray_free(attrs);
    }
    if (notfirst) {
        slapi_ch_free_string(&object);
    }
    slapi_ch_free_string(&superior);
    siz += 18; /* objectclass: top\n\0 */
    len = strlen(templateentry);
    templateentry = (char *)slapi_ch_realloc(templateentry, siz);
    p = templateentry + len;
    PR_snprintf(p, siz - len, "objectclass: top\n");

    e = slapi_str2entry(templateentry, SLAPI_STR2ENTRY_NOT_WELL_FORMED_LDIF);
    /* set the template entry to send the result to clients */
    slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_ENTRY, e);
bailout:
    slapi_ch_free_string(&templateentry);
    return rc;
}

int
acl_get_effective_rights(
    Slapi_PBlock *pb,
    Slapi_Entry *e,                             /* target entry */
    char **attrs,                               /* Attribute of    the entry */
    struct berval *val __attribute__((unused)), /* value of attr. NOT USED */
    int access __attribute__((unused)),         /* requested access rights */
    char **errbuf)
{
    Slapi_PBlock *gerpb = NULL;
    void *aclcb = NULL;
    char *subjectndn = NULL;
    char *gerstr = NULL;
    size_t gerstrsize = 0;
    size_t gerstrcap = 0;
    int iscritical = 0; /* critical may be missing or false http://tools.ietf.org/html/draft-ietf-ldapext-acl-model-08 */
    int rc = LDAP_SUCCESS;

    *errbuf = NULL;

    if (NULL == e) /* create a template entry from SLAPI_SEARCH_GERATTRS */
    {
        rc = _ger_generate_template_entry(pb);
        slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_ENTRY, &e);
        if (rc != LDAP_SUCCESS || NULL == e) {
            goto bailout;
        }
    }

    /*
     * Get the subject
     */
    rc = _ger_parse_control(pb, &subjectndn, &iscritical, errbuf);
    if (rc != LDAP_SUCCESS) {
        goto bailout;
    }

    /*
     * The requestor should have g permission on the entry
     * to get the effective rights.
     */
    rc = _ger_g_permission_granted(pb, e, subjectndn, errbuf);
    if (rc != LDAP_SUCCESS) {
        goto bailout;
    }

    /*
     * Construct a new pb
     */
    rc = _ger_new_gerpb(pb, e, subjectndn, &gerpb, &aclcb, errbuf);
    if (rc != LDAP_SUCCESS) {
        goto bailout;
    }

    /* Get entry level effective rights */
    _ger_get_entry_rights(gerpb, e, subjectndn, &gerstr, &gerstrsize, &gerstrcap, errbuf);

    /*
     * Attribute level effective rights may not be NULL
     * even if entry level's is.
     */
    _ger_get_attrs_rights(gerpb, e, subjectndn, attrs, &gerstr, &gerstrsize, &gerstrcap, errbuf);

bailout:
    /*
     * Now construct the response control
     */
    _ger_set_response_control(pb, iscritical, rc);

    if (rc != LDAP_SUCCESS) {
        gerstr = slapi_ch_smprintf("entryLevelRights: %d\nattributeLevelRights: *:%d", rc, rc);
    }

    slapi_log_err(SLAPI_LOG_ACLSUMMARY, plugin_name,
                  "###### Effective Rights on Entry (%s) for Subject (%s) ######\n",
                  e ? slapi_entry_get_ndn(e) : "null", subjectndn ? subjectndn : "null");
    slapi_log_err(SLAPI_LOG_ACLSUMMARY, plugin_name, "%s\n", gerstr);

    /* Restore pb */
    _ger_release_gerpb(&gerpb, &aclcb, pb);

    /*
     * General plugin uses SLAPI_RESULT_TEXT for error text. Here
     * SLAPI_PB_RESULT_TEXT is exclusively shared with add, dse and schema.
     * slapi_pblock_set() will free any previous data, and
     * pblock_done() will free SLAPI_PB_RESULT_TEXT.
     */
    slapi_pblock_set(pb, SLAPI_PB_RESULT_TEXT, gerstr);

    if (!iscritical) {
        /*
         * If return code is not LDAP_SUCCESS, the server would
         * abort sending the data of the entry to the client.
         */
        rc = LDAP_SUCCESS;
    }

    slapi_ch_free_string(&subjectndn);
    slapi_ch_free_string(&gerstr);
    return rc;
}
