/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * 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

/*
 *  File: modrdn.c
 *
 *  Functions:
 *
 *      ldif_back_modrdn() - ldap ldif back-end modrdn routine
 *      rdn2typval() - rdn to typval converter
 *      ldif_add_mod() - Adds a modification to be performed.
 *
 */

#include "back-ldif.h"
int rdn2typval(char *, char **, struct berval *);
void ldif_add_mod(LDAPMod ***, int, char *, struct berval **);

/*
 *  Function: ldif_back_modrdn
 *
 *  Returns: returns 0 if good, -1 else.
 *
 *  Description: For changetype: modrdn, this modifies the rdn of the entry
 */
int
ldif_back_modrdn(Slapi_PBlock *pb)
{
    LDIF *db; /*ldif backend database*/
    ldif_Entry *prev, *tprev, *entry, *entry2, *test;
    char *pdn, *newdn;        /*Used for dn manipulation*/
    char *dn, *newrdn, *type; /*Used for dn manipulation*/
    int i;                    /*A counter*/
    char **rdns, **dns;       /*Used for dn manipulation*/
    int deleteoldrdn;         /*Flag from user to delete old rdn*/
    struct berval bv;
    struct berval *bvps[2];
    LDAPMod **mods; /*Holds the list of modifications*/
    int rc;

    slapi_log_err(SLAPI_LOG_TRACE, "=> ldif_back_modrdn\n", 0, 0, 0);

    prev = NULL;

    /*Get the information from the front end, including the database*/
    if (slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &db) < 0 ||
        slapi_pblock_get(pb, SLAPI_MODRDN_TARGET, &dn) < 0 ||
        slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &newrdn) < 0 ||
        slapi_pblock_get(pb, SLAPI_MODRDN_DELOLDRDN, &deleteoldrdn) < 0) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        return (-1);
    }

    /*Lock the database*/
    PR_Lock(db->ldif_lock);

    /*
   * Find the entry we are about to modify
   * prev will point to the previous element in the list,
   * NULL if there is no previous element
   */
    if ((entry = (ldif_Entry *)ldif_find_entry(pb, db, dn, &prev)) == NULL) {
        slapi_send_ldap_result(pb, LDAP_NO_SUCH_OBJECT, NULL, NULL, 0, NULL);
        PR_Unlock(db->ldif_lock);
        return (-1);
    }

    /*Make sure that we are trying to modify the rdn of a leaf.*/
    if (has_children(db, entry)) {
        slapi_send_ldap_result(pb, LDAP_NOT_ALLOWED_ON_NONLEAF, NULL, NULL, 0, NULL);
        PR_Unlock(db->ldif_lock);
        return (-1);
    }


    /* Create a copy of the entry and apply the changes to it */
    if ((entry2 = (ldif_Entry *)ldifentry_dup(entry)) == NULL) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        PR_Unlock(db->ldif_lock);
        return (-1);
    }

    /*Check the access*/
    rc = slapi_access_allowed(pb, entry2->lde_e, NULL, NULL, SLAPI_ACL_WRITE);
    if (rc != LDAP_SUCCESS) {
        slapi_send_ldap_result(pb, rc, NULL, NULL, 0, NULL);
        ldifentry_free(entry2);
        PR_Unlock(db->ldif_lock);
        return (-1);
    }

    /* Construct the new dn */
    if ((pdn = slapi_dn_beparent(pb, dn)) != NULL) {

        /* parent + rdn + separator(s) + null */
        newdn = (char *)malloc(strlen(pdn) + strlen(newrdn) + 3);
        if (newdn == NULL) {
            slapi_log_err(SLAPI_LOG_ERR, "malloc failed", 0, 0, 0);
            exit(1);
        }

        strcpy(newdn, newrdn);
        strcat(newdn, ", ");
        strcat(newdn, pdn);
    } else {
        newdn = strdup(newrdn);
    }
    free(pdn);

    /*Normalize the newdn, that is, squeeze out all unnecessary spaces*/
    (void)slapi_dn_normalize(newdn);


    /* Add the new dn to our working copy of the entry */
    slapi_entry_set_dn(entry2->lde_e, newdn);


    /* See if an entry with the new name already exists */
    if ((test = (ldif_Entry *)ldif_find_entry(pb, db, newdn, &tprev)) != NULL) {
        slapi_send_ldap_result(pb, LDAP_ALREADY_EXISTS, NULL, NULL, 0, NULL);

        goto error_return;
    }


    /*
   * Delete old rdn values from the entry if deleteoldrdn is set.
   * Add new rdn values to the entry.
   */
    mods = NULL;
    bvps[0] = &bv;
    bvps[1] = NULL;
    if ((dns = slapi_ldap_explode_dn(dn, 0)) != NULL) {
        if ((rdns = slapi_ldap_explode_rdn(dns[0], 0)) != NULL) {
            for (i = 0; rdns[i] != NULL; i++) {

                /* Delete from entry attributes */
                if (deleteoldrdn && rdn2typval(rdns[i], &type, &bv) == 0) {
                    ldif_add_mod(&mods, LDAP_MOD_DELETE, type, bvps);
                }
            }
            slapi_ldap_value_free(rdns);
        }
        slapi_ldap_value_free(dns);
    }
    if (dns == NULL || rdns == NULL) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        goto error_return;
    }
    /* Add new rdn values to the entry */
    if ((rdns = slapi_ldap_explode_rdn(newrdn, 0)) != NULL) {
        for (i = 0; rdns[i] != NULL; i++) {
            /* Add to entry */
            if (rdn2typval(rdns[i], &type, &bv) == 0) {
                ldif_add_mod(&mods, LDAP_MOD_ADD, type, bvps);
            }
        }
        slapi_ldap_value_free(rdns);
    } else {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);
        goto error_return;
    }
    bv.bv_val = newdn;
    bv.bv_len = strlen(newdn);
    ldif_add_mod(&mods, LDAP_MOD_REPLACE, "entrydn", bvps);

    /* Check for abandon */
    if (slapi_op_abandoned(pb)) {
        goto error_return;
    }

    /* Apply the mods we built above to the copy of the entry */
    if (apply_mods(entry2->lde_e, mods) != 0) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);

        goto error_return;
    }

    /* Update the database and the disk */
    if (update_db(pb, db, entry2, prev, LDIF_DB_REPLACE) != 0) {
        slapi_send_ldap_result(pb, LDAP_OPERATIONS_ERROR, NULL, NULL, 0, NULL);

        goto error_return;
    }

    /*Unlock the database, and tell the user the good news*/
    PR_Unlock(db->ldif_lock);
    slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL);
    slapi_log_err(SLAPI_LOG_TRACE, "<= ldif_back_modrdn\n", 0, 0, 0);
    return (0);

error_return:;

    /* Result already sent above - just free stuff */
    PR_Unlock(db->ldif_lock);
    ldifentry_free(entry2);

    return (-1);
}

/*
 *  Function: rdn2typval
 *
 *  Returns: returns 0 if good, -1 else.
 *
 *  Description: converts an rdn to a typeval
 */
int
rdn2typval(char *rdn, char **type, struct berval *bv)
{
    char *s;

    if ((s = strchr(rdn, '=')) == NULL) {
        return (-1);
    }
    *s++ = '\0';

    *type = rdn;
    bv->bv_val = s;
    bv->bv_len = strlen(s);

    return (0);
}

/*
 *  Function: ldif_add_mod
 *
 *  Returns: void
 *
 *  Description: Adds a modification (add, delete, etc) to the list
 *               of modifications that will eventually be made to some entry
 */
void
ldif_add_mod(LDAPMod ***modlist, int modtype, char *type, struct berval **bvps)
{
    int i;

    for (i = 0; modlist[i] != NULL; i++) {
        ; /* NULL */
    }

    *modlist = (LDAPMod **)realloc((char *)*modlist,
                                   (i + 2) * sizeof(LDAPMod *));

    if (*modlist == NULL) {
        slapi_log_err(SLAPI_LOG_ERR, "realloc failed", 0, 0, 0);
        exit(1);
    }
    (*modlist)[i] = (LDAPMod *)malloc(sizeof(LDAPMod));

    if ((*modlist)[i] == NULL) {
        slapi_log_err(SLAPI_LOG_ERR, "malloc failed", 0, 0, 0);
        exit(1);
    }

    (*modlist)[i]->mod_type = (char *)strdup(type);
    if ((*modlist)[i]->mod_type == NULL) {
        slapi_log_err(SLAPI_LOG_ERR, "strdup failed", 0, 0, 0);
        exit(1);
    }


    (*modlist)[i]->mod_op = modtype;
    (*modlist)[i]->mod_bvalues = (struct berval **)malloc(2 * sizeof(struct berval *));
    if ((*modlist)[i]->mod_bvalues == NULL) {
        slapi_log_err(SLAPI_LOG_ERR, "malloc failed", 0, 0, 0);
        exit(1);
    }
    (*modlist)[i]->mod_bvalues[0] = ber_bvdup(bvps[0]);
    (*modlist)[i]->mod_bvalues[1] = NULL;
    (*modlist)[i + 1] = NULL;
}
