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

#include <stdio.h>
#include <string.h>
#include "portable.h"
#include "slapi-plugin.h"
#include "slap.h"
#include "prthread.h"
#include "prlock.h"
#include "prerror.h"
#include "prcvar.h"
#include "prio.h"
#include <sys/stat.h>

#define REFERINT_DEFAULT_FILE_MODE S_IRUSR | S_IWUSR
#define REFERINT_PLUGIN_SUBSYSTEM "referint-plugin" /* used for logging */
#define REFERINT_PREOP_DESC       "referint preop plugin"
#define REFERINT_ATTR_DELAY       "referint-update-delay"
#define REFERINT_ATTR_LOGFILE     "referint-logfile"
#define REFERINT_ATTR_MEMBERSHIP  "referint-membership-attr"
#define MAX_LINE     2048
#define READ_BUFSIZE 4096
#define MY_EOF  0
#define STARTUP 2

typedef struct referint_config
{
    int delay;
    char *logfile;
    char **attrs;
} referint_config;

Slapi_RWLock *config_rwlock = NULL;

/* function prototypes */
int referint_postop_init(Slapi_PBlock *pb);
int referint_postop_del(Slapi_PBlock *pb);
int referint_postop_modrdn(Slapi_PBlock *pb);
int referint_postop_start(Slapi_PBlock *pb);
int referint_postop_close(Slapi_PBlock *pb);
int update_integrity(Slapi_DN *sDN, char *newrDN, Slapi_DN *newsuperior, Slapi_PBlock *pb);
int GetNextLine(char *dest, int size_dest, PRFileDesc *stream);
int my_fgetc(PRFileDesc *stream);
void referint_thread_func(void *arg);
void writeintegritylog(Slapi_PBlock *pb, char *logfilename, Slapi_DN *sdn, char *newrdn, Slapi_DN *newsuperior, Slapi_DN *requestorsdn);
int load_config(Slapi_PBlock *pb, Slapi_Entry *config_entry, int apply);
int referint_get_delay(void);
char *referint_get_logfile(void);
char **referint_get_attrs(void);
int referint_postop_modify(Slapi_PBlock *pb);
int referint_validate_config(Slapi_PBlock *pb);
static int referint_preop_init(Slapi_PBlock *pb);
void referint_set_config_area(Slapi_DN *dn);
Slapi_DN *referint_get_config_area(void);
void referint_set_plugin_area(Slapi_DN *sdn);
Slapi_DN *referint_get_plugin_area(void);
int referint_sdn_config_cmp(Slapi_DN *sdn);
void referint_get_config(int *delay, char **logfile);

/* global thread control stuff */
static PRLock *referint_mutex = NULL;
static PRThread *referint_tid = NULL;
static pthread_mutex_t keeprunning_mutex;
static pthread_cond_t keeprunning_cv;

static int keeprunning = 0;
static uint64_t batch_thread_running = 0;
static referint_config *config = NULL;
static Slapi_DN *_ConfigAreaDN = NULL;
static Slapi_DN *_pluginDN = NULL;

static Slapi_PluginDesc pdesc = {"referint", VENDOR, DS_PACKAGE_VERSION, "referential integrity plugin"};
static int allow_repl = 0;
static Slapi_DN **plugin_EntryScope = NULL;
static Slapi_DN *plugin_ExcludeEntryScope = NULL;
static Slapi_DN *plugin_ContainerScope = NULL;
static void *referint_plugin_identity = NULL;
static int use_txn = 0;
static int premodfn = SLAPI_PLUGIN_PRE_MODIFY_FN;


static void
referint_lock(void)
{
    if (use_txn) { /* no lock if betxn is enabled */
        return;
    }
    if (NULL == referint_mutex) {
        referint_mutex = PR_NewLock();
    }
    if (referint_mutex) {
        PR_Lock(referint_mutex);
    }
}

static void
referint_unlock(void)
{
    if (use_txn) { /* no lock if betxn is enabled */
        return;
    }
    if (referint_mutex) {
        PR_Unlock(referint_mutex);
    }
}

void
referint_set_config_area(Slapi_DN *dn)
{
    slapi_rwlock_wrlock(config_rwlock);
    slapi_sdn_free(&_ConfigAreaDN);
    _ConfigAreaDN = slapi_sdn_dup(dn);
    slapi_rwlock_unlock(config_rwlock);
}

/*
 * No need to lock here, because this only called from referint_sdn_config_cmp()
 * which does take the lock.
 */
Slapi_DN *
referint_get_config_area(void)
{
    return _ConfigAreaDN;
}

/* no locking needed for the plugin DN because it is set at initialization */
void
referint_set_plugin_area(Slapi_DN *sdn)
{
    slapi_sdn_free(&_pluginDN);
    _pluginDN = slapi_sdn_dup(sdn);
}

Slapi_DN *
referint_get_plugin_area(void)
{
    return _pluginDN;
}

int
referint_postop_init(Slapi_PBlock *pb)
{
    Slapi_Entry *plugin_entry = NULL;
    const char *plugin_type = NULL;
    int delfn = SLAPI_PLUGIN_POST_DELETE_FN;
    int mdnfn = SLAPI_PLUGIN_POST_MODRDN_FN;
    int modfn = SLAPI_PLUGIN_POST_MODIFY_FN; /* for config changes */
    char *preop_plugin_type = "preoperation";

    /*
     *  Get plugin identity and stored it for later use.
     *  Used for internal operations.
     */
    slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &referint_plugin_identity);
    PR_ASSERT(referint_plugin_identity);

    if ((config = (referint_config *)slapi_ch_calloc(1, sizeof(referint_config))) == NULL) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_postop_init  - Failed to "
                                                                "allocate configuration\n");
        return (-1);
    }

    /* get the args */
    if ((slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_ENTRY, &plugin_entry) == 0) &&
        plugin_entry &&
        (plugin_type = slapi_entry_attr_get_ref(plugin_entry, "nsslapd-plugintype")) &&
        plugin_type && strstr(plugin_type, "betxn")) {
        delfn = SLAPI_PLUGIN_BE_TXN_POST_DELETE_FN;
        mdnfn = SLAPI_PLUGIN_BE_TXN_POST_MODRDN_FN;
        modfn = SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN;
        preop_plugin_type = "betxnpreoperation";
        premodfn = SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN;
        use_txn = 1;
    }

    if (plugin_entry) {
        char *plugin_attr_value;
        char **plugin_attr_values;

        plugin_attr_value = (char *)slapi_entry_attr_get_ref(plugin_entry, "nsslapd-pluginAllowReplUpdates");
        if (plugin_attr_value && strcasecmp(plugin_attr_value, "on") == 0) {
            allow_repl = 1;
        }

        plugin_attr_values = slapi_entry_attr_get_charray(plugin_entry, "nsslapd-pluginEntryScope");
        if (plugin_attr_values) {
            int i, j = 0;
            ;
            for (i = 0; plugin_attr_values[i]; i++)
                ;
            plugin_EntryScope = (Slapi_DN **)slapi_ch_calloc(sizeof(Slapi_DN *), i + 1);
            for (i = 0; plugin_attr_values[i]; i++) {
                if (slapi_dn_syntax_check(NULL, plugin_attr_values[i], 1) == 1) {
                    slapi_log_err(SLAPI_LOG_WARNING, REFERINT_PLUGIN_SUBSYSTEM,
                                  "referint_postop_init - Ignoring invalid DN used as plugin entry scope: [%s]\n",
                                  plugin_attr_values[i]);
                    slapi_ch_free_string(&plugin_attr_values[i]);
                } else {
                    plugin_EntryScope[j++] = slapi_sdn_new_dn_passin(plugin_attr_values[i]);
                }
            }
            slapi_ch_free((void **)&plugin_attr_values);
        }
        plugin_attr_value = slapi_entry_attr_get_charptr(plugin_entry, "nsslapd-pluginExcludeEntryScope");
        if (plugin_attr_value) {
            if (slapi_dn_syntax_check(NULL, plugin_attr_value, 1) == 1) {
                slapi_log_err(SLAPI_LOG_WARNING, REFERINT_PLUGIN_SUBSYSTEM,
                              "referint_postop_init - Ignoring invalid DN used as plugin exclude entry scope: [%s]\n",
                              plugin_attr_value);
                slapi_ch_free_string(&plugin_attr_value);
            } else {
                plugin_ExcludeEntryScope = slapi_sdn_new_dn_passin(plugin_attr_value);
            }
        }

        plugin_attr_value = slapi_entry_attr_get_charptr(plugin_entry, "nsslapd-pluginContainerScope");
        if (plugin_attr_value) {
            if (slapi_dn_syntax_check(NULL, plugin_attr_value, 1) == 1) {
                slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                              "referint_postop_init - Ignoring invalid DN used as plugin container scope: [%s]\n",
                              plugin_attr_value);
                slapi_ch_free_string(&plugin_attr_value);
            } else {
                plugin_ContainerScope = slapi_sdn_new_dn_passin(plugin_attr_value);
            }
        }

        referint_set_plugin_area(slapi_entry_get_sdn(plugin_entry));
    }

    if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc) != 0 ||
        slapi_pblock_set(pb, delfn, (void *)referint_postop_del) != 0 ||
        slapi_pblock_set(pb, mdnfn, (void *)referint_postop_modrdn) != 0 ||
        slapi_pblock_set(pb, modfn, (void *)(void *)referint_postop_modify) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)referint_postop_start) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *)referint_postop_close) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_postop_init - Failed\n");
        return (-1);
    }

    /*
     * Setup the preop plugin for config validation
     */
    if (slapi_register_plugin(preop_plugin_type,     /* op type */
                              1,                     /* Enabled */
                              "referint_preop_init", /* this function desc */
                              referint_preop_init,   /* init func */
                              REFERINT_PREOP_DESC,   /* plugin desc */
                              NULL,                  /* ? */
                              referint_plugin_identity /* access control */)) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_postop_init - Preop registration failed\n");
        return (-1);
    }

    return (0);
}

/*
 * referint-update-delay: 0
 * referint-logfile: /var/log/dirsrv/slapd-localhost/referint
 * referint-membership-attr: member
 * referint-membership-attr: uniquemember
 * referint-membership-attr: owner
 * referint-membership-attr: seeAlso
 *
 *
 * Need to lock this!
 */
int
load_config(Slapi_PBlock *pb, Slapi_Entry *config_entry, int apply)
{
    referint_config *tmp_config = NULL;
    char *value = NULL;
    char **attrs = NULL;
    char **argv = NULL;
    int new_config_present = 0;
    int argc = 0;
    int rc = SLAPI_PLUGIN_SUCCESS;

    if (config_entry == NULL) {
        return rc;
    }

    slapi_rwlock_wrlock(config_rwlock);

    if (config == NULL) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                      "load_config: config is NULL\n");
        rc = SLAPI_PLUGIN_FAILURE;
        goto done;
    }
    if ((tmp_config = (referint_config *)slapi_ch_calloc(1, sizeof(referint_config))) == NULL) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "load_config - Failed to "
                                                                "allocate configuration\n");
        rc = SLAPI_PLUGIN_FAILURE;
        goto done;
    } else {
        /* set this for config validation */
        tmp_config->delay = -2;
    }


    if ((value = (char *)slapi_entry_attr_get_ref(config_entry, REFERINT_ATTR_DELAY))) {
        char *endptr = NULL;
        tmp_config->delay = strtol(value, &endptr, 10);
        if (!(value && !*endptr) || tmp_config->delay < -1) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "load_config - invalid value \"%s\" for %s; should be >= -1\n",
                          value, REFERINT_ATTR_DELAY);
            rc = SLAPI_PLUGIN_FAILURE;
            goto done;
        }
        new_config_present = 1;
    }
    if ((value = slapi_entry_attr_get_charptr(config_entry, REFERINT_ATTR_LOGFILE))) {
        tmp_config->logfile = value;
        new_config_present = 1;
    }
    if ((attrs = slapi_entry_attr_get_charray(config_entry, REFERINT_ATTR_MEMBERSHIP))) {
        tmp_config->attrs = attrs;
        new_config_present = 1;
    }

    if (new_config_present) {
        /* Verify we have everything we need */
        if (tmp_config->delay == -2) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "load_config - Plugin configuration is missing %s\n",
                          REFERINT_ATTR_DELAY);
            rc = SLAPI_PLUGIN_FAILURE;
        } else if (!tmp_config->logfile) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "load_config - Plugin configuration is missing %s\n",
                          REFERINT_ATTR_LOGFILE);
            rc = SLAPI_PLUGIN_FAILURE;
        } else if (!tmp_config->attrs) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "load_config - Plugin configuration is missing %s\n",
                          REFERINT_ATTR_MEMBERSHIP);
            rc = SLAPI_PLUGIN_FAILURE;
        }
    } else {
        /*
         * We are using the old plugin arg configuration, get the args
         */
        if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGC, &argc) != 0) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "load_config - Failed to get argc\n");
            rc = SLAPI_PLUGIN_FAILURE;
            goto done;
        }
        if (slapi_pblock_get(pb, SLAPI_PLUGIN_ARGV, &argv) != 0) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "load_config - Failed to get argv\n");
            rc = SLAPI_PLUGIN_FAILURE;
            goto done;
        }
        if (argv == NULL) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "load_config - args are NULL\n");
            rc = SLAPI_PLUGIN_FAILURE;
            goto done;
        }
        /*
         * Load the args and set the config struct
         */
        if (argc >= 3) {
            int i;

            tmp_config->delay = atoi(argv[0]);
            tmp_config->logfile = slapi_ch_strdup(argv[1]);
            for (i = 3; argv[i] != NULL; i++) {
                slapi_ch_array_add(&tmp_config->attrs, slapi_ch_strdup(argv[i]));
            }
        } else {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "load_config - Insufficient arguments supplied\n");
            rc = SLAPI_PLUGIN_FAILURE;
            goto done;
        }
    }

done:
    if (apply && rc == SLAPI_PLUGIN_SUCCESS) {
        slapi_ch_free_string(&config->logfile);
        slapi_ch_array_free(config->attrs);
        slapi_ch_free((void **)&config);
        config = tmp_config;
    } else if (tmp_config) {
        slapi_ch_free_string(&tmp_config->logfile);
        slapi_ch_array_free(tmp_config->attrs);
        slapi_ch_free((void **)&tmp_config);
    }

    slapi_rwlock_unlock(config_rwlock);

    return rc;
}

int
referint_postop_modify(Slapi_PBlock *pb)
{
    Slapi_Entry *entry = NULL, *e = NULL;
    Slapi_Entry *config_e = NULL;
    Slapi_DN *config_sdn = NULL;
    Slapi_DN *sdn = NULL;
    char *config_area = NULL;
    int result = 0;
    int rc = SLAPI_PLUGIN_SUCCESS;

    /* check if we are updating the shared config entry */
    slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
    slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &entry);

    if (referint_sdn_config_cmp(sdn) == 0 && slapi_sdn_compare(sdn, referint_get_plugin_area())) {
        if (SLAPI_PLUGIN_FAILURE == load_config(pb, entry, 1)) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_postop_modify - "
                                                                    "Failed to update configuration.\n");
            return SLAPI_PLUGIN_FAILURE;
        }
    } else if (slapi_sdn_compare(sdn, referint_get_plugin_area()) == 0) {
        /*
         * Check if the plugin config area is set(verify it and load its config),
         * otherwise reload the plugin entry config
         */
        if ((config_area = slapi_entry_attr_get_charptr(entry, SLAPI_PLUGIN_SHARED_CONFIG_AREA))) {
            rc = slapi_dn_syntax_check(pb, config_area, 1);
            if (rc) { /* syntax check failed */
                slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_postop_modify - "
                                                                        "%s does not contain a valid DN (%s)\n",
                              SLAPI_PLUGIN_SHARED_CONFIG_AREA, config_area);
                rc = LDAP_INVALID_DN_SYNTAX;
                goto bail;
            }
            config_sdn = slapi_sdn_new_dn_byval(config_area);
            result = slapi_search_internal_get_entry(config_sdn, NULL, &e, referint_plugin_identity);
            if (LDAP_SUCCESS != result) {
                if (result == LDAP_NO_SUCH_OBJECT) {
                    /* log an error and use the plugin entry for the config */
                    slapi_log_err(SLAPI_LOG_PLUGIN, REFERINT_PLUGIN_SUBSYSTEM,
                                  "referint_postop_modify - Config entry \"%s\" does "
                                  "not exist.\n",
                                  config_area);
                    rc = LDAP_OPERATIONS_ERROR;
                    goto bail;
                }
            } else {
                if (e) {
                    config_e = e;
                } else {
                    slapi_log_err(SLAPI_LOG_PLUGIN, REFERINT_PLUGIN_SUBSYSTEM,
                                  "referint_postop_modify - Config entry \"%s\" was not located.\n", config_area);
                    rc = LDAP_OPERATIONS_ERROR;
                    goto bail;
                }
            }
        } else {
            config_e = entry;
        }
        if (load_config(pb, config_e, 1) != LDAP_SUCCESS) {
            rc = LDAP_UNWILLING_TO_PERFORM;
            goto bail;
        }
        referint_set_config_area(slapi_entry_get_sdn(config_e));
    }

bail:
    slapi_ch_free_string(&config_area);
    slapi_sdn_free(&config_sdn);
    slapi_entry_free(e);

    return rc;
}

int
referint_get_delay(void)
{
    int delay;

    slapi_rwlock_rdlock(config_rwlock);
    delay = config->delay;
    slapi_rwlock_unlock(config_rwlock);

    return delay;
}

char *
referint_get_logfile(void)
{
    char *log_file;

    slapi_rwlock_rdlock(config_rwlock);
    log_file = slapi_ch_strdup(config->logfile);
    slapi_rwlock_unlock(config_rwlock);

    return log_file;
}

void
referint_get_config(int *delay, char **logfile)
{
    slapi_rwlock_rdlock(config_rwlock);
    if (delay) {
        *delay = config->delay;
    }
    if (logfile) {
        *logfile = slapi_ch_strdup(config->logfile);
    }
    slapi_rwlock_unlock(config_rwlock);
}

/*
 * might need to find an alternate option instead of copying
 */
char **
referint_get_attrs(void)
{
    char **attrs = NULL;

    slapi_rwlock_rdlock(config_rwlock);
    attrs = slapi_ch_array_dup(config->attrs);
    slapi_rwlock_unlock(config_rwlock);

    return attrs;
}

int
referint_sdn_config_cmp(Slapi_DN *sdn)
{
    int rc = 0;

    slapi_rwlock_rdlock(config_rwlock);
    rc = slapi_sdn_compare(sdn, referint_get_config_area());
    slapi_rwlock_unlock(config_rwlock);

    return rc;
}

int
referint_sdn_in_container_scope(Slapi_DN *sdn)
{
    if (plugin_ContainerScope == NULL) {
        return (1);
    } else {
        return (slapi_sdn_issuffix(sdn, plugin_ContainerScope));
    }
}

int
referint_sdn_in_entry_scope(Slapi_DN *sdn)
{
    int rc = 0;

    if (plugin_ExcludeEntryScope && slapi_sdn_issuffix(sdn, plugin_ExcludeEntryScope))
        return (0);

    if (plugin_EntryScope == NULL) {
        /* no scope defined, all sdns match */
        return (1);
    } else {
        int i = 0;
        while (plugin_EntryScope[i]) {
            if (slapi_sdn_issuffix(sdn, plugin_EntryScope[i])) {
                rc = 1;
                break;
            } else {
                i++;
            }
        }
    }

    return (rc);
}

int
referint_postop_del(Slapi_PBlock *pb)
{
    Slapi_DN *sdn = NULL;
    char *logfile = NULL;
    int delay;
    int isrepop = 0;
    int oprc;
    int rc = SLAPI_PLUGIN_SUCCESS;

    if (slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isrepop) != 0 ||
        slapi_pblock_get(pb, SLAPI_DELETE_TARGET_SDN, &sdn) != 0 ||
        slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                      "referint_postop_del - Could not get parameters\n");
        return SLAPI_PLUGIN_FAILURE;
    }
    /*
     *  This plugin should only execute if the delete was successful
     *  and this is not a replicated op(unless its allowed)
     */
    if (oprc != 0 || (isrepop && !allow_repl)) {
        return SLAPI_PLUGIN_SUCCESS;
    }

    referint_get_config(&delay, NULL);

    if (delay == -1) {
        /* integrity updating is off */
        rc = SLAPI_PLUGIN_SUCCESS;
    } else if (delay == 0) { /* no delay */
        /* call function to update references to entry */
        if (referint_sdn_in_entry_scope(sdn)) {
            rc = update_integrity(sdn, NULL, NULL, pb);
        }
    } else {
        /* write the entry to integrity log */
        logfile = referint_get_logfile();
        writeintegritylog(pb, logfile, sdn, NULL, NULL, NULL /* slapi_get_requestor_sdn(pb) */);
        rc = SLAPI_PLUGIN_SUCCESS;
    }

    slapi_ch_free_string(&logfile);

    return (rc);
}

int
referint_postop_modrdn(Slapi_PBlock *pb)
{
    Slapi_DN *sdn = NULL;
    Slapi_DN *newsuperior;
    char *logfile = NULL;
    char *newrdn;
    int oprc;
    int rc = SLAPI_PLUGIN_SUCCESS;
    int delay;
    int isrepop = 0;

    if (slapi_pblock_get(pb, SLAPI_IS_REPLICATED_OPERATION, &isrepop) != 0 ||
        slapi_pblock_get(pb, SLAPI_MODRDN_TARGET_SDN, &sdn) != 0 ||
        slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &newrdn) != 0 ||
        slapi_pblock_get(pb, SLAPI_MODRDN_NEWSUPERIOR_SDN, &newsuperior) != 0 ||
        slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                      "referint_postop_modrdn - Could not get parameters\n");
        return SLAPI_PLUGIN_FAILURE;
    }
    /*
     *  This plugin should only execute if the delete was successful
     *  and this is not a replicated op (unless its allowed)
     */
    if (oprc != 0 || (isrepop && !allow_repl)) {
        return SLAPI_PLUGIN_SUCCESS;
    }

    referint_get_config(&delay, NULL);

    if (delay == -1) {
        /* integrity updating is off */
        rc = SLAPI_PLUGIN_SUCCESS;
    } else if (delay == 0) { /* no delay */
        /* call function to update references to entry */
        if (!plugin_EntryScope && !plugin_ExcludeEntryScope) {
            /* no scope defined, default always process referint */
            rc = update_integrity(sdn, newrdn, newsuperior, pb);
        } else {
            const char *newsuperiordn = slapi_sdn_get_dn(newsuperior);
            if ((newsuperiordn == NULL && referint_sdn_in_entry_scope(sdn)) ||
                (newsuperiordn && referint_sdn_in_entry_scope(newsuperior))) {
                /*
                 * It is a modrdn inside the scope or into the scope,
                 * process normal modrdn
                 */
                rc = update_integrity(sdn, newrdn, newsuperior, pb);
            } else if (referint_sdn_in_entry_scope(sdn)) {
                /* the entry is moved out of scope, treat as delete */
                rc = update_integrity(sdn, NULL, NULL, pb);
            }
        }
    } else {
        /* write the entry to integrity log */
        logfile = referint_get_logfile();
        writeintegritylog(pb, logfile, sdn, newrdn, newsuperior, NULL /* slapi_get_requestor_sdn(pb) */);
        rc = SLAPI_PLUGIN_SUCCESS;
    }
    slapi_ch_free_string(&logfile);

    return (rc);
}

int
isFatalSearchError(int search_result)
{
    /*   Make sure search result is fatal
     *   Some conditions that happen quite often are not fatal
     *   for example if you have two suffixes and one is null, you will always
     *   get no such object, however this is not a fatal error.
     *   Add other conditions to the if statement as they are found
     */
    switch (search_result) {
    case LDAP_REFERRAL:
    case LDAP_NO_SUCH_OBJECT:
        return 0;
    }
    return 1;
}

static int
_do_modify(Slapi_PBlock *mod_pb, Slapi_DN *entrySDN, LDAPMod **mods)
{
    int rc = 0;
    LDAPMod *mod[2];

    /* Split multiple modifications into individual modify operations */
    for (size_t i = 0; (mods != NULL) && (mods[i] != NULL); i++) {
        mod[0] = mods[i];
        mod[1] = NULL;

        slapi_pblock_init(mod_pb);

        /* Do a single mod with error overrides for DEL/ADD */
        if (allow_repl) {
            rc = slapi_single_modify_internal_override(mod_pb, entrySDN, mod,
                                                        referint_plugin_identity, OP_FLAG_REPLICATED);
        } else {
            rc = slapi_single_modify_internal_override(mod_pb, entrySDN, mod,
                                                        referint_plugin_identity, 0);
        }

        if (rc != LDAP_SUCCESS) {
            return rc;
        }
    }

    return rc;
}

/*
 * update one attribute value per _do_modify
 */
static int
_update_one_per_mod(Slapi_DN *entrySDN, /* DN of the searched entry */
                    Slapi_Attr *attr,   /* referred attribute */
                    char *attrName,
                    Slapi_DN *origDN,        /* original DN that was modified */
                    char *newRDN,            /* new RDN from modrdn */
                    const char *newsuperior, /* new superior from modrdn */
                    Slapi_PBlock *mod_pb)
{
    LDAPMod attribute1, attribute2;
    LDAPMod *list_of_mods[3];
    char *values_del[2];
    char *values_add[2];
    char *newDN = NULL;
    char **dnParts = NULL;
    char *sval = NULL;
    char *newvalue = NULL;
    char *p = NULL;
    size_t dnlen = 0;
    int rc = 0;

    if (NULL == newRDN && NULL == newsuperior) {
        /* in delete mode */
        /* delete old dn so set that up */
        values_del[0] = (char *)slapi_sdn_get_dn(origDN);
        values_del[1] = NULL;
        attribute1.mod_type = attrName;
        attribute1.mod_op = LDAP_MOD_DELETE;
        attribute1.mod_values = values_del;
        list_of_mods[0] = &attribute1;
        /* terminate list of mods. */
        list_of_mods[1] = NULL;
        rc = _do_modify(mod_pb, entrySDN, list_of_mods);
        if (rc) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "_update_one_per_mod - Entry %s: deleting \"%s: %s\" failed (%d)"
                          "\n",
                          slapi_sdn_get_dn(entrySDN), attrName, slapi_sdn_get_dn(origDN), rc);
        }
    } else {
        /* in modrdn mode */
        const char *superior = NULL;
        int nval = 0;
        Slapi_Value *v = NULL;

        if (NULL == origDN) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "_update_one_per_mod - NULL dn was passed\n");
            goto bail;
        }
        /* need to put together rdn into a dn */
        dnParts = slapi_ldap_explode_dn(slapi_sdn_get_dn(origDN), 0);
        if (NULL == dnParts) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "_update_one_per_mod - Failed to explode dn %s\n",
                          slapi_sdn_get_dn(origDN));
            goto bail;
        }
        if (NULL == newRDN) {
            newRDN = dnParts[0];
        }
        if (newsuperior) {
            superior = newsuperior;
        } else {
            /* do not free superior */
            superior = slapi_dn_find_parent(slapi_sdn_get_dn(origDN));
        }
        /* newRDN and superior are already normalized. */
        newDN = slapi_ch_smprintf("%s,%s", newRDN, superior);
        slapi_dn_ignore_case(newDN);
        /*
         * Compare the modified dn with the value of
         * the target attribute of referint to find out
         * the modified dn is the ancestor (case 2) or
         * the value itself (case 1).
         *
         * E.g.,
         * (case 1)
         * modrdn: uid=A,ou=B,o=C --> uid=A',ou=B',o=C
         *            (origDN)             (newDN)
         * member: uid=A,ou=B,ou=C --> uid=A',ou=B',ou=C
         *            (sval)               (newDN)
         *
         * (case 2)
         * modrdn: ou=B,o=C --> ou=B',o=C
         *         (origDN)      (newDN)
         * member: uid=A,ou=B,ou=C --> uid=A,ou=B',ou=C
         *         (sval)              (sval' + newDN)
         */
        for (nval = slapi_attr_first_value(attr, &v); nval != -1;
             nval = slapi_attr_next_value(attr, nval, &v)) {
            int normalize_rc;
            p = NULL;
            dnlen = 0;

            /* DN syntax, which should be a string */
            sval = slapi_ch_strdup(slapi_value_get_string(v));
            normalize_rc = slapi_dn_normalize_case_ext(sval, 0, &p, &dnlen);
            if (normalize_rc == 0) { /* sval is passed in; not terminated */
                *(p + dnlen) = '\0';
                sval = p;
            } else if (normalize_rc > 0) {
                slapi_ch_free_string(&sval);
                sval = p;
            }
            /* else: (normalize_rc < 0) Ignore the DN normalization error for now. */

            p = PL_strstr(sval, slapi_sdn_get_ndn(origDN));
            if (p == sval) {
                /* (case 1) */
                values_del[0] = sval;
                values_del[1] = NULL;
                attribute1.mod_type = attrName;
                attribute1.mod_op = LDAP_MOD_DELETE;
                attribute1.mod_values = values_del;
                list_of_mods[0] = &attribute1;

                values_add[0] = newDN;
                values_add[1] = NULL;
                attribute2.mod_type = attrName;
                attribute2.mod_op = LDAP_MOD_ADD;
                attribute2.mod_values = values_add;
                list_of_mods[1] = &attribute2;
                list_of_mods[2] = NULL;
                rc = _do_modify(mod_pb, entrySDN, list_of_mods);
                if (rc) {
                    slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                                  "_update_one_per_mod - Entry %s: replacing \"%s: %s\" "
                                  "with \"%s: %s\" failed (%d)\n",
                                  slapi_sdn_get_dn(entrySDN), attrName,
                                  slapi_sdn_get_dn(origDN), attrName, newDN, rc);
                }
            } else if (p) {
                char bak;
                /* (case 2) */
                values_del[0] = sval;
                values_del[1] = NULL;
                attribute1.mod_type = attrName;
                attribute1.mod_op = LDAP_MOD_DELETE;
                attribute1.mod_values = values_del;
                list_of_mods[0] = &attribute1;

                bak = *p;
                *p = '\0';
                /* newRDN and superior are already normalized. */
                newvalue = slapi_ch_smprintf("%s%s", sval, newDN);
                *p = bak;
                values_add[0] = newvalue;
                values_add[1] = NULL;
                attribute2.mod_type = attrName;
                attribute2.mod_op = LDAP_MOD_ADD;
                attribute2.mod_values = values_add;
                list_of_mods[1] = &attribute2;
                list_of_mods[2] = NULL;
                rc = _do_modify(mod_pb, entrySDN, list_of_mods);
                if (rc) {
                    slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                                  "_update_one_per_mod - Entry %s: replacing \"%s: %s\" "
                                  "with \"%s: %s\" failed (%d)\n",
                                  slapi_sdn_get_dn(entrySDN), attrName, sval, attrName, newvalue, rc);
                }
                slapi_ch_free_string(&newvalue);
            }
            /* else: value does not include the modified DN.  Ignore it. */
            slapi_ch_free_string(&sval);
        }

        /* cleanup memory allocated for dnParts and newDN */
        if (dnParts) {
            slapi_ldap_value_free(dnParts);
            dnParts = NULL;
        }
        slapi_ch_free_string(&newDN);
    }
bail:
    return rc;
}

/*
 * update multiple attribute values per _do_modify
 */
static int
_update_all_per_mod(Slapi_DN *entrySDN, /* DN of the searched entry */
                    Slapi_Attr *attr,   /* referred attribute */
                    char *attrName,
                    Slapi_DN *origDN,        /* original DN that was modified */
                    char *newRDN,            /* new RDN from modrdn */
                    const char *newsuperior, /* new superior from modrdn */
                    Slapi_PBlock *mod_pb)
{
    Slapi_Mods *smods = NULL;
    char *newDN = NULL;
    char **dnParts = NULL;
    char *sval = NULL;
    char *newvalue = NULL;
    char *p = NULL;
    size_t dnlen = 0;
    int rc = 0;

    if (NULL == newRDN && NULL == newsuperior) {
        /* in delete mode */
        LDAPMod *mods[2];
        char *values_del[2];
        LDAPMod attribute1;

        /* delete old dn so set that up */
        values_del[0] = (char *)slapi_sdn_get_dn(origDN);
        values_del[1] = NULL;
        attribute1.mod_type = attrName;
        attribute1.mod_op = LDAP_MOD_DELETE;
        attribute1.mod_values = values_del;
        mods[0] = &attribute1;
        /* terminate list of mods. */
        mods[1] = NULL;
        rc = _do_modify(mod_pb, entrySDN, mods);
        if (rc) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "_update_all_per_mod - Entry %s: deleting \"%s: %s\" failed (%d)"
                          "\n",
                          slapi_sdn_get_dn(entrySDN), attrName, slapi_sdn_get_dn(origDN), rc);
        }
    } else {
        /* in modrdn mode */
        const char *superior = NULL;
        int nval = 0;
        Slapi_Value *v = NULL;

        if (NULL == origDN) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "_update_all_per_mod - NULL dn was passed\n");
            goto bail;
        }
        /* need to put together rdn into a dn */
        dnParts = slapi_ldap_explode_dn(slapi_sdn_get_dn(origDN), 0);
        if (NULL == dnParts) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "_update_all_per_mod - Failed to explode dn %s\n",
                          slapi_sdn_get_dn(origDN));
            goto bail;
        }
        if (NULL == newRDN) {
            newRDN = dnParts[0];
        }
        if (newsuperior) {
            superior = newsuperior;
        } else {
            /* do not free superior */
            superior = slapi_dn_find_parent(slapi_sdn_get_dn(origDN));
        }
        /* newRDN and superior are already normalized. */
        newDN = slapi_ch_smprintf("%s,%s", newRDN, superior);
        slapi_dn_ignore_case(newDN);
        /*
         * Compare the modified dn with the value of
         * the target attribute of referint to find out
         * the modified dn is the ancestor (case 2) or
         * the value itself (case 1).
         *
         * E.g.,
         * (case 1)
         * modrdn: uid=A,ou=B,o=C --> uid=A',ou=B',o=C
         *            (origDN)             (newDN)
         * member: uid=A,ou=B,ou=C --> uid=A',ou=B',ou=C
         *            (sval)               (newDN)
         *
         * (case 2)
         * modrdn: ou=B,o=C --> ou=B',o=C
         *         (origDN)      (newDN)
         * member: uid=A,ou=B,ou=C --> uid=A,ou=B',ou=C
         *         (sval)              (sval' + newDN)
         */
        slapi_attr_get_numvalues(attr, &nval);
        smods = slapi_mods_new();
        slapi_mods_init(smods, 2 * nval + 1);

        for (nval = slapi_attr_first_value(attr, &v);
             nval != -1;
             nval = slapi_attr_next_value(attr, nval, &v)) {
            int normalize_rc;
            p = NULL;
            dnlen = 0;

            /* DN syntax, which should be a string */
            sval = slapi_ch_strdup(slapi_value_get_string(v));
            normalize_rc = slapi_dn_normalize_case_ext(sval, 0, &p, &dnlen);
            if (normalize_rc == 0) { /* sval is passed in; not terminated */
                *(p + dnlen) = '\0';
                sval = p;
            } else if (normalize_rc > 0) {
                slapi_ch_free_string(&sval);
                sval = p;
            }
            /* else: normalize_rc < 0) Ignore the DN normalization error for now. */

            p = PL_strstr(sval, slapi_sdn_get_ndn(origDN));
            if (p == sval) {
                /* (case 1) */
                slapi_mods_add_string(smods, LDAP_MOD_DELETE, attrName, sval);
                slapi_mods_add_string(smods, LDAP_MOD_ADD, attrName, newDN);
            } else if (p) {
                /* (case 2) */
                slapi_mods_add_string(smods, LDAP_MOD_DELETE, attrName, sval);
                *p = '\0';
                newvalue = slapi_ch_smprintf("%s%s", sval, newDN);
                slapi_mods_add_string(smods, LDAP_MOD_ADD, attrName, newvalue);
                slapi_ch_free_string(&newvalue);
            }
            /* else: value does not include the modified DN.  Ignore it. */
            slapi_ch_free_string(&sval);
        }
        rc = _do_modify(mod_pb, entrySDN, slapi_mods_get_ldapmods_byref(smods));
        if (rc) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "_update_all_per_mod - Entry %s failed (%d)\n",
                          slapi_sdn_get_dn(entrySDN), rc);
        }

        /* cleanup memory allocated for dnParts and newDN */
        if (dnParts) {
            slapi_ldap_value_free(dnParts);
            dnParts = NULL;
        }
        slapi_ch_free_string(&newDN);
        slapi_mods_free(&smods);
    }

bail:
    return rc;
}

int
update_integrity(Slapi_DN *origSDN,
                 char *newrDN,
                 Slapi_DN *newsuperior,
                 Slapi_PBlock *pb)
{
    Slapi_PBlock *search_result_pb = NULL;
    Slapi_PBlock *mod_pb = slapi_pblock_new();
    Slapi_Entry **search_entries = NULL;
    Slapi_DN *sdn = NULL;
    Slapi_Attr *attr = NULL;
    void *node = NULL;
    const char *origDN = slapi_sdn_get_dn(origSDN);
    const char *search_base = NULL;
    char *attrName = NULL;
    char *filter = NULL;
    char *attrs[2];
    char **membership_attrs = NULL;
    int search_result;
    int nval = 0;
    int i, j;
    int rc = SLAPI_PLUGIN_SUCCESS;

    membership_attrs = referint_get_attrs();
    /*
     *  For now, just putting attributes to keep integrity on in conf file,
     *  until resolve the other timing mode issue
     */
    search_result_pb = slapi_pblock_new();

    /* Search each namingContext in turn
     * or use the defined scope(s)
     */
    if (plugin_ContainerScope) {
        sdn = plugin_ContainerScope;
    } else {
        sdn = slapi_get_first_suffix(&node, 0);
    }
    while (sdn) {
        Slapi_Backend *be = slapi_be_select(sdn);
        search_base = slapi_sdn_get_dn(sdn);

        for (i = 0; membership_attrs[i] != NULL; i++) {
            if (newrDN) {
                /* we need to check the children of the old dn, so use a wildcard */
                filter = slapi_filter_sprintf("(%s=*%s%s)", membership_attrs[i], ESC_NEXT_VAL, origDN);
            } else {
                filter = slapi_filter_sprintf("(%s=%s%s)", membership_attrs[i], ESC_NEXT_VAL, origDN);
            }
            if (filter) {
                /* Need only the current attribute and its subtypes */
                attrs[0] = membership_attrs[i];
                attrs[1] = NULL;

                /* Use new search API */
                slapi_pblock_init(search_result_pb);
                slapi_pblock_set(search_result_pb, SLAPI_BACKEND, be);
                slapi_search_internal_set_pb(search_result_pb, search_base,
                                             LDAP_SCOPE_SUBTREE, filter, attrs, 0 /* attrs only */,
                                             NULL, NULL, referint_plugin_identity, 0);
                slapi_search_internal_pb(search_result_pb);

                slapi_pblock_get(search_result_pb, SLAPI_PLUGIN_INTOP_RESULT, &search_result);

                /* if search successfull then do integrity update */
                if (search_result == LDAP_SUCCESS) {
                    slapi_pblock_get(search_result_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES,
                                     &search_entries);

                    for (j = 0; search_entries[j] != NULL; j++) {
                        attr = NULL;
                        attrName = NULL;
                        /*
                         *  Loop over all the attributes of the entry and search
                         *  for the integrity attribute and its subtypes
                         */
                        for (slapi_entry_first_attr(search_entries[j], &attr); attr;
                             slapi_entry_next_attr(search_entries[j], attr, &attr)) {
                            /*
                             *  Take into account only the subtypes of the attribute
                             *  in argv[i] having the necessary value  - origDN
                             */
                            slapi_attr_get_type(attr, &attrName);
                            if (slapi_attr_type_cmp(membership_attrs[i], attrName,
                                                    SLAPI_TYPE_CMP_SUBTYPE) == 0) {
                                nval = 0;
                                slapi_attr_get_numvalues(attr, &nval);
                                /*
                                 * We want to reduce the "modify" call as much as
                                 * possible. But if an entry contains 1000s of
                                 * attributes which need to be updated by the
                                 * referint plugin (e.g., a group containing 1000s
                                 * of members), we want to avoid to allocate too
                                 * many mods * in one "modify" call.
                                 * This is a compromise: If an attribute type has
                                 * more than 128 values, we update the attribute
                                 * value one by one. Otherwise, update all values
                                 * in one "modify" call.
                                 */
                                if (nval > 128) {
                                    rc = _update_one_per_mod(
                                        slapi_entry_get_sdn(search_entries[j]),
                                        attr, attrName, origSDN, newrDN,
                                        slapi_sdn_get_dn(newsuperior),
                                        mod_pb);
                                } else {
                                    rc = _update_all_per_mod(
                                        slapi_entry_get_sdn(search_entries[j]),
                                        attr, attrName, origSDN, newrDN,
                                        slapi_sdn_get_dn(newsuperior),
                                        mod_pb);
                                }
                                if (rc) {
                                    if (use_txn) {
                                        /*
                                         * We're using backend transactions,
                                         * so we need to stop on failure.
                                         */
                                        if (pb) {
                                            /* Set the error code of the failure */
                                            slapi_pblock_set(pb, SLAPI_RESULT_CODE, &rc);
                                        }
                                        rc = SLAPI_PLUGIN_FAILURE;
                                        goto free_and_return;
                                    } else {
                                        rc = SLAPI_PLUGIN_SUCCESS;
                                    }
                                }
                            }
                        }
                    }
                } else {
                    if (isFatalSearchError(search_result)) {
                        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                                      "update_integrity - Search (base=%s filter=%s) returned "
                                      "error %d\n",
                                      search_base, filter, search_result);
                        slapi_free_search_results_internal(search_result_pb);
                        if (pb) {
                            slapi_pblock_set(pb, SLAPI_RESULT_CODE, &search_result);
                        }
                        rc = SLAPI_PLUGIN_FAILURE;
                        goto free_and_return;
                    }
                }
                slapi_ch_free_string(&filter);
                slapi_free_search_results_internal(search_result_pb);
            }
        }
        if (plugin_ContainerScope) {
            /* at the moment only a single scope is supported
             * so the loop ends after the first iteration
             */
            sdn = NULL;
        } else {
            sdn = slapi_get_next_suffix(&node, 0);
        }
    }


free_and_return:
    /* free filter and search_results_pb */
    slapi_ch_free_string(&filter);
    slapi_ch_array_free(membership_attrs);

    slapi_pblock_destroy(mod_pb);
    slapi_pblock_destroy(search_result_pb);
    return (rc);
}

int
referint_postop_start(Slapi_PBlock *pb)
{
    Slapi_Entry *plugin_entry = NULL;
    Slapi_Entry *config_e = NULL;
    Slapi_PBlock *search_pb = NULL;
    Slapi_Entry *e = NULL;
    Slapi_DN *config_sdn = NULL;
    char *config_area = NULL;
    int result = 0;
    int rc = 0;

    if ((config_rwlock = slapi_new_rwlock()) == NULL) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_postop_start - Failed to "
                                                                "create rwlock.\n");
        return (-1);
    }

    slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &plugin_entry);

    /* Set the alternate config area if one is defined. */
    slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_AREA, &config_area);
    if (config_area) {
        rc = slapi_dn_syntax_check(pb, config_area, 1);
        if (rc) { /* syntax check failed */
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_postop_start - "
                                                                    "%s does not contain a valid DN (%s)\n",
                          SLAPI_PLUGIN_SHARED_CONFIG_AREA, config_area);
            rc = LDAP_INVALID_DN_SYNTAX;
            goto bail;
        }
        config_sdn = slapi_sdn_new_dn_byval(config_area);
        result = slapi_search_internal_get_entry(config_sdn, NULL, &e, referint_plugin_identity);
        if (LDAP_SUCCESS != result) {
            if (result == LDAP_NO_SUCH_OBJECT) {
                /* log an error and use the plugin entry for the config */
                slapi_log_err(SLAPI_LOG_PLUGIN, REFERINT_PLUGIN_SUBSYSTEM,
                              "referint_postop_start -Config entry \"%s\" does "
                              "not exist.\n",
                              config_area);
                rc = -1;
                goto bail;
            }
        } else {
            if (e) {
                config_e = e;
            } else {
                slapi_log_err(SLAPI_LOG_PLUGIN, REFERINT_PLUGIN_SUBSYSTEM,
                              "referint_postop_start - Config entry \"%s\" was not located.\n", config_area);
                rc = -1;
                goto bail;
            }
        }
    } else {
        config_e = plugin_entry;
    }
    if (load_config(pb, config_e, STARTUP) != LDAP_SUCCESS) {
        rc = -1;
        goto bail;
    }
    referint_set_config_area(slapi_entry_get_sdn(config_e));

    /*
     *  Only bother to start the thread if you are in delay mode.
     *     0  = no delay,
     *     -1 = integrity off
     */
    if (referint_get_delay() > 0) {
        pthread_condattr_t condAttr;

        /* initialize the cv and lock */
        if (!use_txn && (NULL == referint_mutex)) {
            referint_mutex = PR_NewLock();
        }
        if ((rc = pthread_mutex_init(&keeprunning_mutex, NULL)) != 0) {
            slapi_log_err(SLAPI_LOG_ERR, "referint_postop_start",
                          "cannot create new lock.  error %d (%s)\n",
                          rc, strerror(rc));
            exit(1);
        }
        if ((rc = pthread_condattr_init(&condAttr)) != 0) {
            slapi_log_err(SLAPI_LOG_ERR, "referint_postop_start",
                          "cannot create new condition attribute variable.  error %d (%s)\n",
                          rc, strerror(rc));
            exit(1);
        }
        if ((rc = pthread_condattr_setclock(&condAttr, CLOCK_MONOTONIC)) != 0) {
            slapi_log_err(SLAPI_LOG_ERR, "referint_postop_start",
                          "cannot set condition attr clock.  error %d (%s)\n",
                          rc, strerror(rc));
            exit(1);
        }
        if ((rc = pthread_cond_init(&keeprunning_cv, &condAttr)) != 0) {
            slapi_log_err(SLAPI_LOG_ERR, "referint_postop_start",
                          "cannot create new condition variable.  error %d (%s)\n",
                          rc, strerror(rc));
            exit(1);
        }
        pthread_condattr_destroy(&condAttr); /* no longer needed */

        keeprunning = 1;

        referint_tid = PR_CreateThread(PR_USER_THREAD,
                                       referint_thread_func,
                                       NULL,
                                       PR_PRIORITY_NORMAL,
                                       PR_GLOBAL_THREAD,
                                       PR_UNJOINABLE_THREAD,
                                       SLAPD_DEFAULT_THREAD_STACKSIZE);
        if (referint_tid == NULL) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "referint_postop_start - PR_CreateThread failed\n");
            exit(1);
        }
    }

bail:
    slapi_free_search_results_internal(search_pb);
    slapi_pblock_destroy(search_pb);
    slapi_sdn_free(&config_sdn);
    slapi_entry_free(e);

    return rc;
}

int
referint_postop_close(Slapi_PBlock *pb __attribute__((unused)))
{
    /* signal the batch thread to exit */
    if (referint_get_delay() > 0) {
        pthread_mutex_lock(&keeprunning_mutex);
        keeprunning = 0;
        pthread_cond_signal(&keeprunning_cv);
        pthread_mutex_unlock(&keeprunning_mutex);
    }

    /* waiting for the batch thread to exit */
    while (1) {
        if (slapi_atomic_load_64(&batch_thread_running, __ATOMIC_ACQUIRE) == 0) {
            /* batch thread exited */
            break;
        }

        DS_Sleep(PR_MillisecondsToInterval(1000));
    }



    slapi_destroy_rwlock(config_rwlock);
    config_rwlock = NULL;

    slapi_ch_free_string(&config->logfile);
    slapi_ch_array_free(config->attrs);
    slapi_ch_free((void **)&config);


    return (0);
}

void
referint_thread_func(void *arg __attribute__((unused)))
{
    PRFileDesc *prfd = NULL;
    char *logfilename = NULL;
    char thisline[MAX_LINE];
    char delimiter[] = "\t\n";
    char *ptoken;
    char *tmprdn;
    char *iter = NULL;
    Slapi_DN *sdn = NULL;
    Slapi_DN *tmpsuperior = NULL;
    struct timespec current_time = {0};
    int delay;
    int no_changes;

    slapi_atomic_store_64(&batch_thread_running, 1, __ATOMIC_RELEASE);

    /*
     * keep running this thread until plugin is signaled to close
     */
    while (1) {
        /*
         * In case of shutdown, plugin close function (referint_postop_close)
         * is waiting for the end of that thread to do the cleanup
         */
        pthread_mutex_lock(&keeprunning_mutex);
        if (keeprunning == 0) {
            pthread_mutex_unlock(&keeprunning_mutex);
            break;
        }
        pthread_mutex_unlock(&keeprunning_mutex);

        /* refresh the config */
        slapi_ch_free_string(&logfilename);
        referint_get_config(&delay, &logfilename);

        no_changes = 1;
        while (no_changes) {

            pthread_mutex_lock(&keeprunning_mutex);
            if (keeprunning == 0) {
                pthread_mutex_unlock(&keeprunning_mutex);
                break;
            }
            pthread_mutex_unlock(&keeprunning_mutex);

            referint_lock();
            if ((prfd = PR_Open(logfilename, PR_RDONLY, REFERINT_DEFAULT_FILE_MODE)) == NULL) {
                referint_unlock();
                /* go back to sleep and wait for this file */
                pthread_mutex_lock(&keeprunning_mutex);
                clock_gettime(CLOCK_MONOTONIC, &current_time);
                current_time.tv_sec += delay;
                pthread_cond_timedwait(&keeprunning_cv, &keeprunning_mutex, &current_time);
                pthread_mutex_unlock(&keeprunning_mutex);
            } else {
                no_changes = 0;
            }
        }
        /*
         *  Check keep running here, because after break out of no
         *  changes loop on shutdown, also need to break out of this
         *  loop before trying to do the changes. The server
         *  will pick them up on next startup as file still exists
         */
        pthread_mutex_lock(&keeprunning_mutex);
        if (keeprunning == 0) {
            pthread_mutex_unlock(&keeprunning_mutex);
            break;
        }
        pthread_mutex_unlock(&keeprunning_mutex);

        while (GetNextLine(thisline, MAX_LINE, prfd)) {
            ptoken = ldap_utf8strtok_r(thisline, delimiter, &iter);
            sdn = slapi_sdn_new_normdn_byref(ptoken);
            ptoken = ldap_utf8strtok_r(NULL, delimiter, &iter);

            if (ptoken == NULL) {
                /* Invalid line in referint log, skip it */
                slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                        "Skipping invalid referint log line: (%s)\n", thisline);
                slapi_sdn_free(&sdn);
                continue;
            }
            if (!strcasecmp(ptoken, "NULL")) {
                tmprdn = NULL;
            } else {
                tmprdn = slapi_ch_smprintf("%s", ptoken);
            }

            ptoken = ldap_utf8strtok_r(NULL, delimiter, &iter);
            if (!strcasecmp(ptoken, "NULL")) {
                tmpsuperior = NULL;
            } else {
                tmpsuperior = slapi_sdn_new_normdn_byref(ptoken);
            }
            ptoken = ldap_utf8strtok_r(NULL, delimiter, &iter);
            if (strcasecmp(ptoken, "NULL") != 0) {
                /* Set the bind DN in the thread data */
                if (slapi_td_set_dn(slapi_ch_strdup(ptoken))) {
                    slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_thread_func - "
                                                                            "Failed to set thread data\n");
                }
            }

            update_integrity(sdn, tmprdn, tmpsuperior, NULL);

            slapi_sdn_free(&sdn);
            slapi_ch_free_string(&tmprdn);
            slapi_sdn_free(&tmpsuperior);
        }

        PR_Close(prfd);

        /* remove the original file */
        if (PR_SUCCESS != PR_Delete(logfilename)) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          "referint_thread_func - Could not delete \"%s\"\n", logfilename);
        }

        /* unlock and let other writers back at the file */
        referint_unlock();

        /* wait on condition here */
        pthread_mutex_lock(&keeprunning_mutex);
        clock_gettime(CLOCK_MONOTONIC, &current_time);
        current_time.tv_sec += delay;
        pthread_cond_timedwait(&keeprunning_cv, &keeprunning_mutex, &current_time);
        pthread_mutex_unlock(&keeprunning_mutex);
    }

    slapi_atomic_store_64(&batch_thread_running, 0, __ATOMIC_RELEASE);

    /* cleanup resources allocated in start  */
    pthread_mutex_destroy(&keeprunning_mutex);
    pthread_cond_destroy(&keeprunning_cv);
    slapi_ch_free_string(&logfilename);
}

int
my_fgetc(PRFileDesc *stream)
{
    /* This is equivalent to memset of 0, but statically defined. */
    static char buf[READ_BUFSIZE] = {0};
    static int position = READ_BUFSIZE;
    int retval;
    int err;

    /* check if we need to load the buffer */
    if (READ_BUFSIZE == position) {
        if ((err = PR_Read(stream, buf, READ_BUFSIZE)) >= 0) {
            /* it read some data */;
            position = 0;
        } else {
            /* an error occurred */
            return err;
        }
    }

    /* try to read some data */
    if ('\0' == buf[position]) {
        /* out of data, return eof */
        retval = MY_EOF;
        position = READ_BUFSIZE;
    } else {
        retval = buf[position];
        position++;
    }

    return retval;
}

int
GetNextLine(char *dest, int size_dest, PRFileDesc *stream)
{

    char nextchar = '\0';
    int done = 0;
    int i = 0;

    while (!done) {
        if ((nextchar = my_fgetc(stream)) != 0) {
            if (i < (size_dest - 1)) {
                dest[i] = nextchar;
                i++;
                if (nextchar == '\n') {
                    /* end of line reached */
                    done = 1;
                }
            } else {
                /* no more room in buffer */
                done = 1;
            }
        } else {
            /* error or end of file */
            done = 1;
        }
    }
    dest[i] = '\0';

    /* return size of string read */
    return i;
}

/*
 *  Write this record to the log file
 */
void
writeintegritylog(Slapi_PBlock *pb, char *logfilename, Slapi_DN *sdn, char *newrdn, Slapi_DN *newsuperior, Slapi_DN *requestorsdn)
{
    PRFileDesc *prfd;
    char buffer[MAX_LINE];
    int len_to_write = 0;
    int rc;
    const char *requestordn = NULL;
    const char *newsuperiordn = NULL;
    size_t reqdn_len = 0;

    if (!(referint_sdn_in_entry_scope(sdn) ||
          (newsuperior && referint_sdn_in_entry_scope(newsuperior)))) {
        return;
    }
    /*
     * Use this lock to protect file data when update integrity is occuring.
     * If betxn is enabled, this mutex is ignored; transaction itself takes
     * the role.
     */
    referint_lock();
    if ((prfd = PR_Open(logfilename, PR_WRONLY | PR_CREATE_FILE | PR_APPEND,
                        REFERINT_DEFAULT_FILE_MODE)) == NULL) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                      "writeintegritylog - Could not write integrity log \"%s\" " SLAPI_COMPONENT_NAME_NSPR " %d (%s)\n",
                      logfilename, PR_GetError(), slapd_pr_strerror(PR_GetError()));

        PR_Unlock(referint_mutex);
        referint_unlock();
        return;
    }
    /*
     *  Make sure we have enough room in our buffer before trying to write it.
     *  add length of dn +  5(three tabs, a newline, and terminating \0)
     */
    len_to_write = slapi_sdn_get_ndn_len(sdn) + 5;

    newsuperiordn = slapi_sdn_get_dn(newsuperior);
    if (newsuperiordn &&
        !referint_sdn_in_entry_scope(newsuperior)) {
        /* this is a modrdn which moves the entry out of scope, handle like a delete */
        newsuperiordn = NULL;
        newrdn = NULL;
    }
    if (newrdn == NULL) {
        /* add the length of "NULL" */
        len_to_write += 4;
    } else {
        /* add the length of the newrdn */
        len_to_write += strlen(newrdn);
    }
    if (NULL == newsuperiordn) {
        /* add the length of "NULL" */
        len_to_write += 4;
    } else {
        /* add the length of the newsuperior */
        len_to_write += slapi_sdn_get_ndn_len(newsuperior);
    }
    slapi_pblock_get(pb, SLAPI_REQUESTOR_DN, &requestordn);
    if (requestorsdn && (requestordn = slapi_sdn_get_udn(requestorsdn)) &&
        (reqdn_len = strlen(requestordn))) {
        len_to_write += reqdn_len;
    } else {
        len_to_write += 4; /* "NULL" */
    }

    if (len_to_write > MAX_LINE) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                      "writeintegritylog - Could not write integrity log:"
                      " line length exceeded. It will not be able"
                      " to update references to this entry.\n");
    } else {
        PR_snprintf(buffer, MAX_LINE, "%s\t%s\t%s\t%s\t\n", slapi_sdn_get_dn(sdn),
                    (newrdn != NULL) ? newrdn : "NULL",
                    (newsuperiordn != NULL) ? newsuperiordn : "NULL",
                    requestordn ? requestordn : "NULL");
        if (PR_Write(prfd, buffer, strlen(buffer)) < 0) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                          " writeintegritylog - PR_Write failed : The disk"
                          " may be full or the file is unwritable :: NSPR error - %d\n",
                          PR_GetError());
        }
    }

    /* If file descriptor is closed successfully, PR_SUCCESS */
    rc = PR_Close(prfd);
    if (rc != PR_SUCCESS) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                      " writeintegritylog - Failed to close the file descriptor prfd; NSPR error - %d\n",
                      PR_GetError());
    }
    referint_unlock();
}

static int
referint_preop_init(Slapi_PBlock *pb)
{
    int status = 0;

    if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&pdesc) != 0 ||
        slapi_pblock_set(pb, premodfn, (void *)referint_validate_config) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM,
                      "referint_preop_init - Failed to register plugin\n");
        status = -1;
    }

    return status;
}

/*
 * This is our preop function to validate a config update, postop modify
 * will apply the config change.
 */
int
referint_validate_config(Slapi_PBlock *pb)
{
    Slapi_Entry *config_e = NULL, *e = NULL;
    Slapi_Entry *pre_entry = NULL;
    Slapi_Entry *resulting_entry = NULL;
    Slapi_DN *config_sdn = NULL;
    Slapi_DN *sdn = NULL;
    Slapi_Mods *smods = NULL;
    LDAPMod **mods = NULL;
    char *config_area = NULL;
    int rc = SLAPI_PLUGIN_SUCCESS;

    slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn);
    slapi_pblock_get(pb, SLAPI_ENTRY_PRE_OP, &pre_entry);
    if (!pre_entry) {
        slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_validate_config - Null pre op entry.\n");
        rc = LDAP_OPERATIONS_ERROR;
        goto bail;
    }

    if (referint_sdn_config_cmp(sdn) == 0 && slapi_sdn_compare(sdn, referint_get_plugin_area())) {
        /*
         * This is the shared config entry.  Apply the mods and set/validate
         * the config
         */
        slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
        smods = slapi_mods_new();
        slapi_mods_init_byref(smods, mods);
        /*
         * Create a copy of the entry and apply the
         * mods to create the resulting entry.
         */
        resulting_entry = slapi_entry_dup(pre_entry);
        if (mods && (slapi_entry_apply_mods(resulting_entry, mods) != LDAP_SUCCESS)) {
            /* we don't care about this, the update is invalid and will be caught later */
            goto bail;
        }

        if (SLAPI_PLUGIN_FAILURE == load_config(pb, resulting_entry, 0)) {
            slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_validate_config - "
                                                                    "configuration validation failed.\n");
            rc = LDAP_UNWILLING_TO_PERFORM;
            goto bail;
        }
    } else if (slapi_sdn_compare(sdn, referint_get_plugin_area()) == 0) {
        /*
          * Check if the plugin config area is set(verify it and load its config),
          * otherwise reload the plugin entry config
          */
        slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
        smods = slapi_mods_new();
        slapi_mods_init_byref(smods, mods);

        /* Apply the mods to create the resulting entry. */
        resulting_entry = slapi_entry_dup(pre_entry);
        if (mods && (slapi_entry_apply_mods(resulting_entry, mods) != LDAP_SUCCESS)) {
            /* we don't care about this, the update is invalid and will be caught later */
            goto bail;
        }

        if ((config_area = (char *)slapi_entry_attr_get_ref(resulting_entry, SLAPI_PLUGIN_SHARED_CONFIG_AREA))) {
            rc = slapi_dn_syntax_check(pb, config_area, 1);
            if (rc) { /* syntax check failed */
                slapi_log_err(SLAPI_LOG_ERR, REFERINT_PLUGIN_SUBSYSTEM, "referint_validate_config - "
                                                                        "%s does not contain a valid DN (%s)\n",
                              SLAPI_PLUGIN_SHARED_CONFIG_AREA, config_area);
                rc = LDAP_INVALID_DN_SYNTAX;
                goto bail;
            }
            config_sdn = slapi_sdn_new_dn_byval(config_area);
            rc = slapi_search_internal_get_entry(config_sdn, NULL, &e, referint_plugin_identity);
            if (LDAP_SUCCESS != rc) {
                /* log an error and use the plugin entry for the config */
                slapi_log_err(SLAPI_LOG_PLUGIN, REFERINT_PLUGIN_SUBSYSTEM,
                              "referint_validate_config - Config entry \"%s\" couild not be found, error %d\n",
                              config_area, rc);
                rc = LDAP_OPERATIONS_ERROR;
                goto bail;
            } else {
                if (e) {
                    config_e = e;
                } else {
                    slapi_log_err(SLAPI_LOG_PLUGIN, REFERINT_PLUGIN_SUBSYSTEM,
                                  "referint_validate_config - Config entry \"%s\" was not located.\n", config_area);
                    rc = LDAP_OPERATIONS_ERROR;
                    goto bail;
                }
            }
        } else {
            config_e = resulting_entry;
        }
        if (load_config(pb, config_e, 0) != LDAP_SUCCESS) {
            rc = LDAP_UNWILLING_TO_PERFORM;
            goto bail;
        }
        referint_set_config_area(slapi_entry_get_sdn(config_e));
    }

bail:
    slapi_entry_free(e);
    slapi_entry_free(resulting_entry);
    slapi_sdn_free(&config_sdn);
    slapi_mods_free(&smods);

    return rc;
}
