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


/* windows_private.c */

#include "repl5.h"
#include "slap.h"
#include "slapi-plugin.h"
#include "winsync-plugin.h"
#include "windowsrepl.h"

struct windowsprivate
{

    Slapi_DN *windows_subtree;   /* DN of synchronized subtree  (on the windows side) */
    Slapi_DN *directory_subtree; /* DN of synchronized subtree on directory side */
                                 /* this simplifies the mapping as it's simply
                                  from the former to the latter container, or
                                  vice versa */
    ber_int_t dirsync_flags;
    ber_int_t dirsync_maxattributecount;
    char *dirsync_cookie;
    int dirsync_cookie_len;
    PRBool dirsync_cookie_has_more;
    PRBool create_users_from_dirsync;
    PRBool create_groups_from_dirsync;
    PRBool flatten_tree;
    char *windows_domain;
    int isnt4;
    int iswin2k3;
    /* This filter is used to determine if an entry belongs to this agreement.  We put it here
   * so we only have to allocate each filter once instead of doing it every time we receive a change. */
    Slapi_Filter *directory_filter; /* Used for checking if local entries need to be sync'd to AD */
    Slapi_Filter *windows_filter;   /* Used for checking if remote entries need to be sync'd to DS */
    Slapi_Filter *deleted_filter;   /* Used for checking if an entry is an AD tombstone */
    Slapi_Entry *raw_entry;         /* "raw" un-schema processed last entry read from AD */
    int keep_raw_entry;             /* flag to control when the raw entry is set */
    void *api_cookie;               /* private data used by api callbacks */
    time_t sync_interval;           /* how often to run the dirsync search, in seconds */
    int one_way;                    /* Indicates if this is a one-way agreement and which direction it is */
    int move_action;                /* Indicates what to do with DS entry if AD entry is moved out of scope */
    Slapi_Entry *curr_entry;        /* entry being retrieved; used for the range retrieval */
    char **range_attrs;             /* next attributes for the range retrieval */
    char *windows_userfilter;
    char *directory_userfilter;
    struct subtreepair *subtree_pairs; /* Array of subtree pairs (winSyncSubtreePair) */
    Slapi_DN *windows_treetop;         /* Common subtree top to sync on AD */
                                       /* If winSyncSubtreePair is not set, identical to windows_subtree.
                                * If set, ancestor node of all AD subtrees. */
    Slapi_DN *directory_treetop;       /* Common subtree top to sync on DS */
                                       /* If winSyncSubtreePair is not set, identical to directory_subtree.
                                * If set, ancestor node of all DS subtrees. */
};

static void windows_private_set_windows_domain(const Repl_Agmt *ra, char *domain);
static subtreePair *create_subtree_pairs(char **pairs);
static void free_subtree_pairs(subtreePair **pairs);

static int
true_value_from_string(char *val)
{
    if (strcasecmp(val, "on") == 0 || strcasecmp(val, "yes") == 0 ||
        strcasecmp(val, "true") == 0 || strcasecmp(val, "1") == 0) {
        return 1;
    } else {
        return 0;
    }
}

/* yech - can't declare a constant string array because type_nsds7XX variables
   are not constant strings - so have to build a lookup table */
static int
get_next_disallow_attr_type(int *ii, const char **type)
{
    switch (*ii) {
    case 0:
        *type = type_nsds7WindowsReplicaArea;
        break;
    case 1:
        *type = type_nsds7DirectoryReplicaArea;
        break;
    case 2:
        *type = type_nsds7WindowsDomain;
        break;
    default:
        *type = NULL;
        break;
    }

    if (*type) {
        (*ii)++;
        return 1;
    }
    return 0;
}

static int
check_update_allowed(Repl_Agmt *ra, const char *type, Slapi_Entry *e, int *retval)
{
    int rc = 1;

    /* note - it is not an error to defer setting the value in the ra */
    *retval = 1;
    if (agmt_get_update_in_progress(ra)) {
        const char *distype = NULL;
        int ii = 0;
        while (get_next_disallow_attr_type(&ii, &distype)) {
            if (slapi_attr_types_equivalent(type, distype)) {
                const char *tmpstr = slapi_entry_attr_get_ref(e, type);
                slapi_log_err(SLAPI_LOG_REPL, windows_repl_plugin_name,
                              "windows_parse_config_entry: setting %s to %s will be "
                              "deferred until current update is completed\n",
                              type, tmpstr);
                rc = 0;
                break;
            }
        }
    }

    return rc;
}

static int
windows_parse_config_entry(Repl_Agmt *ra, const char *type, Slapi_Entry *e)
{
    char *tmpstr = NULL;
    int retval = 0;

    if (!check_update_allowed(ra, type, e, &retval)) {
        return retval;
    }

    /*
     * if winSyncSubtreePair is set, WindowsReplicaArea and DirectoryReplicaArea
     * are ignored.
     */
    if (type == NULL || slapi_attr_types_equivalent(type, type_nsds7WindowsReplicaArea)) {
        tmpstr = slapi_entry_attr_get_charptr(e, type_nsds7WindowsReplicaArea);
        if (NULL != tmpstr) {
            windows_private_set_windows_subtree(ra, slapi_sdn_new_dn_passin(tmpstr));
        }
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_nsds7DirectoryReplicaArea)) {
        tmpstr = slapi_entry_attr_get_charptr(e, type_nsds7DirectoryReplicaArea);
        if (NULL != tmpstr) {
            windows_private_set_directory_subtree(ra, slapi_sdn_new_dn_passin(tmpstr));
        }
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_nsds7CreateNewUsers)) {
        tmpstr = (char *)slapi_entry_attr_get_ref(e, type_nsds7CreateNewUsers);
        if (NULL != tmpstr && true_value_from_string(tmpstr)) {
            windows_private_set_create_users(ra, PR_TRUE);
        } else {
            windows_private_set_create_users(ra, PR_FALSE);
        }
        /* If protocol is NULL; the agreement is not started yet.
         * So, no need to notify. */
        if (agmt_get_protocol(ra)) {
            prot_notify_agmt_changed(agmt_get_protocol(ra), (char *)agmt_get_long_name(ra));
        }
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_nsds7CreateNewGroups)) {
        tmpstr = (char *)slapi_entry_attr_get_ref(e, type_nsds7CreateNewGroups);
        if (NULL != tmpstr && true_value_from_string(tmpstr)) {
            windows_private_set_create_groups(ra, PR_TRUE);
        } else {
            windows_private_set_create_groups(ra, PR_FALSE);
        }
        /* If protocol is NULL; the agreement is not started yet.
         * So, no need to notify. */
        if (agmt_get_protocol(ra)) {
            prot_notify_agmt_changed(agmt_get_protocol(ra), (char *)agmt_get_long_name(ra));
        }
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_nsds7WindowsDomain)) {
        tmpstr = slapi_entry_attr_get_charptr(e, type_nsds7WindowsDomain);
        if (NULL != tmpstr) {
            windows_private_set_windows_domain(ra, tmpstr);
        }
        /* No need to free tmpstr because it was aliased by the call above */
        tmpstr = NULL;
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_winSyncInterval)) {
        tmpstr = (char *)slapi_entry_attr_get_ref(e, type_winSyncInterval);
        if (NULL != tmpstr) {
            windows_private_set_sync_interval(ra, tmpstr);
            /* If protocol is NULL; the agreement is not started yet.
             * So, no need to notify. */
            if (agmt_get_protocol(ra)) {
                prot_notify_agmt_changed(agmt_get_protocol(ra), (char *)agmt_get_long_name(ra));
            }
        }
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_oneWaySync)) {
        tmpstr = (char *)slapi_entry_attr_get_ref(e, type_oneWaySync);
        if (NULL != tmpstr) {
            if (strcasecmp(tmpstr, "fromWindows") == 0) {
                windows_private_set_one_way(ra, ONE_WAY_SYNC_FROM_AD);
            } else if (strcasecmp(tmpstr, "toWindows") == 0) {
                windows_private_set_one_way(ra, ONE_WAY_SYNC_TO_AD);
            } else {
                slapi_log_err(SLAPI_LOG_WARNING, windows_repl_plugin_name,
                              "windows_parse_config_entry - Ignoring illegal setting for %s attribute in replication "
                              "agreement \"%s\".  Valid values are \"toWindows\" or "
                              "\"fromWindows\".\n",
                              type_oneWaySync, slapi_entry_get_dn(e));
                windows_private_set_one_way(ra, ONE_WAY_SYNC_DISABLED);
            }
        } else {
            windows_private_set_one_way(ra, ONE_WAY_SYNC_DISABLED);
        }
        /* If protocol is NULL; the agreement is not started yet.
         * So, no need to notify. */
        if (agmt_get_protocol(ra)) {
            prot_notify_agmt_changed(agmt_get_protocol(ra), (char *)agmt_get_long_name(ra));
        }
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_winsyncMoveAction)) {
        tmpstr = (char *)slapi_entry_attr_get_ref(e, type_winsyncMoveAction);
        if (NULL != tmpstr) {
            if (strcasecmp(tmpstr, "delete") == 0) {
                windows_private_set_move_action(ra, MOVE_DOES_DELETE);
            } else if (strcasecmp(tmpstr, "unsync") == 0) {
                windows_private_set_move_action(ra, MOVE_DOES_UNSYNC);
            } else if (strcasecmp(tmpstr, "none") == 0) {
                windows_private_set_move_action(ra, MOVE_DOES_NOTHING);
            } else {
                slapi_log_err(SLAPI_LOG_WARNING, windows_repl_plugin_name,
                              "windows_parse_config_entry - Ignoring illegal setting for %s attribute in replication "
                              "agreement \"%s\".  Valid values are \"delete\" or "
                              "\"unsync\".\n",
                              type_winsyncMoveAction, slapi_entry_get_dn(e));
                windows_private_set_move_action(ra, MOVE_DOES_NOTHING);
            }
        } else {
            windows_private_set_move_action(ra, MOVE_DOES_NOTHING);
        }
        /* If protocol is NULL; the agreement is not started yet.
         * So, no need to notify. */
        if (agmt_get_protocol(ra)) {
            prot_notify_agmt_changed(agmt_get_protocol(ra), (char *)agmt_get_long_name(ra));
        }
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_winSyncWindowsFilter)) {
        tmpstr = slapi_entry_attr_get_charptr(e, type_winSyncWindowsFilter);
        windows_private_set_windows_userfilter(ra, tmpstr); /* if NULL, set it */
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_winSyncDirectoryFilter)) {
        tmpstr = slapi_entry_attr_get_charptr(e, type_winSyncDirectoryFilter);
        windows_private_set_directory_userfilter(ra, tmpstr); /* if NULL, set it */
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_winSyncSubtreePair)) {
        char **parray = slapi_entry_attr_get_charray(e, type_winSyncSubtreePair);

        /* If winSyncSubtreePair is not set, subtree_pairs is NULL */
        windows_private_set_subtreepairs(ra, parray);
        slapi_ch_array_free(parray);
        retval = 1;
    }
    if (type == NULL || slapi_attr_types_equivalent(type, type_winSyncFlattenTree)) {
        tmpstr = (char *)slapi_entry_attr_get_ref(e, type_winSyncFlattenTree);
        if (NULL != tmpstr && true_value_from_string(tmpstr)) {
            windows_private_set_flatten_tree(ra, PR_TRUE);
        } else {
            windows_private_set_flatten_tree(ra, PR_FALSE);
        }
        retval = 1;
    }

    windows_private_set_windows_treetop(ra, NULL);
    windows_private_set_directory_treetop(ra, NULL);

    return retval;
}

/* Returns non-zero if the modify was ok, zero if not */
int
windows_handle_modify_agreement(Repl_Agmt *ra, const char *type, Slapi_Entry *e)
{
    /* Is this a Windows agreement ? */
    if (get_agmt_agreement_type(ra) == REPLICA_TYPE_WINDOWS) {
        return windows_parse_config_entry(ra, type, e);
    } else {
        return 0;
    }
}

void
windows_update_done(Repl_Agmt *agmt, int is_total __attribute__((unused)))
{
    /* "flush" the changes made during the update to the agmt */
    /* get the agmt entry */
    Slapi_DN *agmtdn = slapi_sdn_dup(agmt_get_dn_byref(agmt));
    Slapi_Entry *agmte = NULL;
    int rc = slapi_search_internal_get_entry(agmtdn, NULL, &agmte,
                                             repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION));
    if ((rc == 0) && agmte) {
        int ii = 0;
        const char *distype = NULL;
        while (get_next_disallow_attr_type(&ii, &distype)) {
            windows_handle_modify_agreement(agmt, distype, agmte);
        }
    }
    slapi_entry_free(agmte);
    slapi_sdn_free(&agmtdn);
}

void
windows_init_agreement_from_entry(Repl_Agmt *ra, Slapi_Entry *e)
{
    agmt_set_priv(ra, windows_private_new());

    windows_parse_config_entry(ra, NULL, e);

    windows_plugin_init(ra);
}

const char *
windows_private_get_purl(const Repl_Agmt *ra)
{
    const char *windows_purl;
    char *hostname;

    hostname = agmt_get_hostname(ra);
    if (slapi_is_ipv6_addr(hostname)) {
        /* need to put brackets around the ipv6 address */
        windows_purl = slapi_ch_smprintf("ldap://[%s]:%d", hostname, agmt_get_port(ra));
    } else {
        windows_purl = slapi_ch_smprintf("ldap://%s:%d", hostname, agmt_get_port(ra));
    }
    slapi_ch_free_string(&hostname);

    return windows_purl;
}

Dirsync_Private *
windows_private_new()
{
    Dirsync_Private *dp;

    dp = (Dirsync_Private *)slapi_ch_calloc(sizeof(Dirsync_Private), 1);

    dp->dirsync_maxattributecount = -1;
    dp->directory_filter = NULL;
    dp->windows_filter = NULL;
    dp->deleted_filter = NULL;
    dp->sync_interval = PERIODIC_DIRSYNC_INTERVAL;
    dp->windows_userfilter = NULL;
    dp->directory_userfilter = NULL;
    dp->subtree_pairs = NULL;
    dp->windows_treetop = NULL;
    dp->directory_treetop = NULL;

    return dp;
}

void
windows_agreement_delete(Repl_Agmt *ra)
{
    const subtreePair *sp;

    Dirsync_Private *dp = (Dirsync_Private *)agmt_get_priv(ra);

    PR_ASSERT(dp != NULL);

    winsync_plugin_call_destroy_agmt_cb(ra, dp->directory_subtree,
                                        dp->windows_subtree);

    windows_plugin_cleanup_agmt(ra);

    slapi_sdn_free(&dp->directory_subtree);
    slapi_sdn_free(&dp->windows_subtree);
    slapi_filter_free(dp->directory_filter, 1);
    slapi_filter_free(dp->windows_filter, 1);
    slapi_filter_free(dp->deleted_filter, 1);
    slapi_entry_free(dp->raw_entry);
    slapi_ch_free_string(&dp->windows_domain);
    dp->raw_entry = NULL;
    dp->api_cookie = NULL;
    slapi_ch_free_string(&dp->dirsync_cookie);
    dp->dirsync_cookie_len = 0;

    slapi_ch_free_string(&dp->windows_userfilter);
    slapi_ch_free_string(&dp->directory_userfilter);
    slapi_sdn_free((Slapi_DN **)&dp->windows_treetop);
    slapi_sdn_free((Slapi_DN **)&dp->directory_treetop);
    for (sp = dp->subtree_pairs; sp && sp->ADsubtree && sp->DSsubtree; sp++) {
        slapi_sdn_free((Slapi_DN **)&sp->ADsubtree);
        slapi_sdn_free((Slapi_DN **)&sp->DSsubtree);
    }
    slapi_ch_free((void **)&dp->subtree_pairs);
    slapi_ch_free((void **)&dp);

}

int
windows_private_get_isnt4(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->isnt4;
}

void
windows_private_set_isnt4(const Repl_Agmt *ra, int isit)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    dp->isnt4 = isit;
}

int
windows_private_get_iswin2k3(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->iswin2k3;
}

void
windows_private_set_iswin2k3(const Repl_Agmt *ra, int isit)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    dp->iswin2k3 = isit;
}

/* Returns a copy of the Slapi_Filter pointer.  The caller should not free it */
Slapi_Filter *
windows_private_get_directory_filter(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    if (dp->directory_filter == NULL) {
        char *string_filter = NULL;
        const char *userfilter = windows_private_get_directory_userfilter(ra);
        if (userfilter) {
            if ('(' == *userfilter) {
                string_filter = slapi_ch_smprintf("(&(|(objectclass=ntuser)(objectclass=ntgroup))(ntUserDomainId=*)%s)",
                                                  userfilter);
            } else {
                string_filter = slapi_ch_smprintf("(&(|(objectclass=ntuser)(objectclass=ntgroup))(ntUserDomainId=*)(%s))",
                                                  userfilter);
            }
        } else {
            string_filter = slapi_ch_strdup("(&(|(objectclass=ntuser)(objectclass=ntgroup))(ntUserDomainId=*))");
        }
        /* The filter gets freed in windows_agreement_delete() */
        dp->directory_filter = slapi_str2filter(string_filter);
        slapi_ch_free_string(&string_filter);
    }

    return dp->directory_filter;
}

/* Returns a copy of the Slapi_Filter pointer.  The caller should not free it */
Slapi_Filter *
windows_private_get_windows_filter(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    if (dp->windows_filter == NULL) {
        const char *userfilter = windows_private_get_windows_userfilter(ra);
        if (userfilter) {
            char *string_filter = NULL;
            if ('(' == *userfilter) {
                string_filter = slapi_ch_strdup(userfilter);
            } else {
                string_filter = slapi_ch_smprintf("(%s)", userfilter);
            }
            /* The filter gets freed in windows_agreement_delete() */
            dp->windows_filter = slapi_str2filter(string_filter);
            slapi_ch_free_string(&string_filter);
        }
    }

    return dp->windows_filter;
}

/* Returns a copy of the Slapi_Filter pointer.  The caller should not free it */
Slapi_Filter *
windows_private_get_deleted_filter(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    if (dp->deleted_filter == NULL) {
        char *string_filter = slapi_ch_strdup("(isdeleted=*)");
        /* The filter gets freed in windows_agreement_delete() */
        dp->deleted_filter = slapi_str2filter(string_filter);
        slapi_ch_free_string(&string_filter);
    }

    return dp->deleted_filter;
}

/* Returns a copy of the Slapi_DN pointer, no need to free it */
const Slapi_DN *
windows_private_get_windows_subtree(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->windows_subtree;
}

const char *
windows_private_get_windows_domain(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->windows_domain;
}

static void
windows_private_set_windows_domain(const Repl_Agmt *ra, char *domain)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    slapi_ch_free_string(&dp->windows_domain);
    dp->windows_domain = domain;
}

/* Returns a copy of the Slapi_DN pointer, no need to free it */
const Slapi_DN *
windows_private_get_directory_subtree(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->directory_subtree;
}

/* Takes a copy of the sdn passed in */
void
windows_private_set_windows_subtree(const Repl_Agmt *ra, Slapi_DN *sdn)
{

    Dirsync_Private *dp;

    PR_ASSERT(ra);
    PR_ASSERT(sdn);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    slapi_sdn_free(&dp->windows_subtree);
    dp->windows_subtree = sdn;
}

/* Takes a copy of the sdn passed in */
void
windows_private_set_directory_subtree(const Repl_Agmt *ra, Slapi_DN *sdn)
{

    Dirsync_Private *dp;

    PR_ASSERT(ra);
    PR_ASSERT(sdn);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    slapi_sdn_free(&dp->directory_subtree);
    dp->directory_subtree = sdn;
}

PRBool
windows_private_create_users(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->create_users_from_dirsync;
}


void
windows_private_set_create_users(const Repl_Agmt *ra, PRBool value)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    dp->create_users_from_dirsync = value;
}

PRBool
windows_private_create_groups(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->create_groups_from_dirsync;
}


void
windows_private_set_create_groups(const Repl_Agmt *ra, PRBool value)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    dp->create_groups_from_dirsync = value;
}


int
windows_private_get_one_way(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->one_way;
}


void
windows_private_set_one_way(const Repl_Agmt *ra, int value)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    dp->one_way = value;
}

const char *
windows_private_get_windows_userfilter(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->windows_userfilter;
}

/* filter is passed in */
void
windows_private_set_windows_userfilter(const Repl_Agmt *ra, char *filter)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    slapi_ch_free_string(&dp->windows_userfilter);
    dp->windows_userfilter = filter;
}

const char *
windows_private_get_directory_userfilter(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->directory_userfilter;
}

/* filter is passed in */
void
windows_private_set_directory_userfilter(const Repl_Agmt *ra, char *filter)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    slapi_ch_free_string(&dp->directory_userfilter);
    dp->directory_userfilter = filter;
}

const subtreePair *
windows_private_get_subtreepairs(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->subtree_pairs;
}

/* parray is NOT passed in; caller frees it. */
void
windows_private_set_subtreepairs(const Repl_Agmt *ra, char **parray)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    free_subtree_pairs(&(dp->subtree_pairs));
    dp->subtree_pairs = create_subtree_pairs(parray);
}

void
windows_private_set_flatten_tree(const Repl_Agmt *ra, PRBool value)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    dp->flatten_tree = value;
}

PRBool
windows_private_get_flatten_tree(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->flatten_tree;
}


/*
 * winSyncSubtreePair: <DS_SUBTREE>:<WINDOWS_SUBTREE>
 * E.g.,
 * winSyncSubtreePair: ou=people,dc=example,dc=com:CN=users,DC=example,DC=com
 * winSyncSubtreePair: ou=adminpeople,dc=example,dc=com:CN=adminusers,DC=example,DC=com
 */
static subtreePair *
create_subtree_pairs(char **pairs)
{
    subtreePair *subtree_pairs = NULL;
    subtreePair *spp;
    char **ptr;
    char *p0, *p1;
    char *saveptr = NULL;
    int cnt;

    for (cnt = 0, ptr = pairs; ptr && *ptr; cnt++, ptr++)
        ;
    if (0 == cnt) {
        return NULL;
    }
    subtree_pairs = (subtreePair *)slapi_ch_calloc(cnt + 1, sizeof(subtreePair));
    spp = subtree_pairs;

    for (ptr = pairs; ptr && *ptr; ptr++) {
        p0 = ldap_utf8strtok_r(*ptr, ":", &saveptr);
        p1 = ldap_utf8strtok_r(NULL, ":", &saveptr);
        if ((NULL == p0) || (NULL == p1)) {
            slapi_log_err(SLAPI_LOG_ERR, windows_repl_plugin_name,
                          "create_subtree_pairs - Ignoring invalid subtree pairs \"%s\".\n", *ptr);
            continue;
        }
        spp->DSsubtree = slapi_sdn_new_dn_byval(p0);
        if (NULL == spp->DSsubtree) {
            slapi_log_err(SLAPI_LOG_ERR, windows_repl_plugin_name,
                          "create_subtree_pairs - Ignoring invalid DS subtree \"%s\".\n", p0);
            continue;
        }
        spp->ADsubtree = slapi_sdn_new_dn_byval(p1);
        if (NULL == spp->ADsubtree) {
            slapi_log_err(SLAPI_LOG_ERR, windows_repl_plugin_name,
                          "create_subtree_pairs - Ignoring invalid AD subtree \"%s\".\n", p1);
            slapi_sdn_free(&(spp->DSsubtree));
            continue;
        }
        spp++;
    }
    return subtree_pairs;
}

static void
free_subtree_pairs(subtreePair **pairs)
{
    subtreePair *p;

    if (NULL == pairs) {
        return;
    }
    /*
     * If exists, the subtree pair is both non-NULL or NULL.
     * Both NULL is the condition to stop the loop.
     */
    for (p = *pairs; p && p->ADsubtree && p->DSsubtree; p++) {
        slapi_sdn_free(&(p->ADsubtree));
        slapi_sdn_free(&(p->DSsubtree));
    }
    slapi_ch_free((void **)pairs);
}

const Slapi_DN *
windows_private_get_windows_treetop(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->windows_treetop;
}

/* treetop is NOT passed in */
void
windows_private_set_windows_treetop(const Repl_Agmt *ra, char *treetop)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    slapi_sdn_free(&(dp->windows_treetop));
    if (treetop) {
        dp->windows_treetop = slapi_sdn_new_dn_byval(treetop);
    } else {
        const subtreePair *subtree_pairs = windows_private_get_subtreepairs(ra);
        const subtreePair *sp;
        if (subtree_pairs) {
            Slapi_DN *treetop_sdn = NULL;
            for (sp = subtree_pairs; sp && sp->ADsubtree; sp++) {
                if (NULL == treetop_sdn) {
                    treetop_sdn = slapi_sdn_dup(sp->ADsubtree);
                } else {
                    Slapi_DN *prev = treetop_sdn;
                    treetop_sdn = slapi_sdn_common_ancestor(prev, sp->ADsubtree);
                    slapi_sdn_free(&prev);
                }
            }
            if (treetop_sdn) {
                dp->windows_treetop = treetop_sdn;
            } else {
                slapi_log_err(SLAPI_LOG_ERR, windows_repl_plugin_name,
                              "windows_private_set_windows_treetop - "
                              "winSyncSubtreePair contains inconsistent Windows subtrees.\n");
                dp->windows_treetop = NULL;
            }
        } else {
            const Slapi_DN *windows_subtree = windows_private_get_windows_subtree(ra);
            dp->windows_treetop = slapi_sdn_dup(windows_subtree);
        }
    }
}

const Slapi_DN *
windows_private_get_directory_treetop(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);
    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->directory_treetop;
}

/* treetop is NOT passed in */
void
windows_private_set_directory_treetop(const Repl_Agmt *ra, char *treetop)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    slapi_sdn_free(&(dp->directory_treetop));
    if (treetop) {
        dp->directory_treetop = slapi_sdn_new_dn_byval(treetop);
    } else {
        const subtreePair *subtree_pairs = windows_private_get_subtreepairs(ra);
        if (subtree_pairs) {
            const subtreePair *sp;
            Slapi_DN *treetop_sdn = NULL;
            for (sp = subtree_pairs; sp && sp->DSsubtree; sp++) {
                if (NULL == treetop_sdn) {
                    treetop_sdn = slapi_sdn_dup(sp->DSsubtree);
                } else {
                    Slapi_DN *prev = treetop_sdn;
                    treetop_sdn = slapi_sdn_common_ancestor(prev, sp->DSsubtree);
                    slapi_sdn_free(&prev);
                }
            }
            if (treetop_sdn) {
                dp->directory_treetop = treetop_sdn;
            } else {
                slapi_log_err(SLAPI_LOG_ERR, windows_repl_plugin_name,
                              "windows_private_set_directory_treetop - "
                              "winSyncSubtreePair contains inconsistent Windows subtrees.\n");
                dp->directory_treetop = NULL;
            }
        } else {
            const Slapi_DN *directory_subtree = windows_private_get_directory_subtree(ra);
            dp->directory_treetop = slapi_sdn_dup(directory_subtree);
        }
    }
}

/*
    This function returns the current Dirsync_Private that's inside
    Repl_Agmt ra as a ldap control.

  */
LDAPControl *
windows_private_dirsync_control(const Repl_Agmt *ra)
{

    LDAPControl *control = NULL;
    BerElement *ber;
    Dirsync_Private *dp;
    char iscritical = PR_TRUE;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);
    ber = ber_alloc();

    ber_printf(ber, "{iio}", dp->dirsync_flags, dp->dirsync_maxattributecount, dp->dirsync_cookie ? dp->dirsync_cookie : "", dp->dirsync_cookie_len);

    /* Use a regular directory server instead of a real AD - for testing */
    if (getenv("WINSYNC_USE_DS")) {
        iscritical = PR_FALSE;
    }
    slapi_build_control(REPL_DIRSYNC_CONTROL_OID, ber, iscritical, &control);

    ber_free(ber, 1);

    return control;
}

/*
    This function scans the array of controls and updates the Repl_Agmt's
    Dirsync_Private if the dirsync control is found.

*/
void
windows_private_update_dirsync_control(const Repl_Agmt *ra, LDAPControl **controls)
{

    Dirsync_Private *dp;
    int foundDirsyncControl;
    int i;
    LDAPControl *dirsync = NULL;
    BerElement *ber = NULL;
    ber_int_t hasMoreData;
    ber_int_t maxAttributeCount;
    BerValue *serverCookie = NULL;
#ifdef FOR_DEBUGGING
    int return_value = LDAP_SUCCESS;
#endif

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    if (NULL != controls) {
        foundDirsyncControl = 0;
        for (i = 0; ((controls[i] != NULL) && (!foundDirsyncControl)); i++) {
            foundDirsyncControl = !strcmp(controls[i]->ldctl_oid, REPL_DIRSYNC_CONTROL_OID);
        }

        if (!foundDirsyncControl) {
#ifdef FOR_DEBUGGING
            return_value = LDAP_CONTROL_NOT_FOUND;
#endif
            goto choke;
        } else if (!controls[i - 1]->ldctl_value.bv_val) {
#ifdef FOR_DEBUGGING
            return_value = LDAP_CONTROL_NOT_FOUND;
#endif
            goto choke;
        } else {
            dirsync = slapi_dup_control(controls[i - 1]);
        }

        if (!dirsync || !BV_HAS_DATA((&(dirsync->ldctl_value)))) {
#ifdef FOR_DEBUGGING
            return_value = LDAP_CONTROL_NOT_FOUND;
#endif
            goto choke;
        }

        ber = ber_init(&dirsync->ldctl_value);

        if (ber_scanf(ber, "{iiO}", &hasMoreData, &maxAttributeCount, &serverCookie) == LBER_ERROR) {
#ifdef FOR_DEBUGGING
            return_value = LDAP_CONTROL_NOT_FOUND;
#endif
            goto choke;
        }

        slapi_ch_free_string(&dp->dirsync_cookie);
        dp->dirsync_cookie = (char *)slapi_ch_malloc(serverCookie->bv_len + 1);

        memcpy(dp->dirsync_cookie, serverCookie->bv_val, serverCookie->bv_len);
        dp->dirsync_cookie_len = (int)serverCookie->bv_len; /* XXX shouldn't cast? */

        /* dp->dirsync_maxattributecount = maxAttributeCount; We don't need to keep this */
        dp->dirsync_cookie_has_more = hasMoreData;

    choke:
        ber_bvfree(serverCookie);
        ber_free(ber, 1);
        ldap_control_free(dirsync);
    } else {
#ifdef FOR_DEBUGGING
        return_value = LDAP_CONTROL_NOT_FOUND;
#endif
    }

#ifdef FOR_DEBUGGING
    slapi_log_err(SLAPI_LOG_TRACE, windows_repl_plugin_name,
                  "<= windows_private_update_dirsync_control - rc=%d\n", return_value);
#endif
}

PRBool
windows_private_dirsync_has_more(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->dirsync_cookie_has_more;
}

void
windows_private_null_dirsync_cookie(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    dp->dirsync_cookie_len = 0;
    slapi_ch_free_string(&dp->dirsync_cookie);
    dp->dirsync_cookie = NULL;
}

static Slapi_Mods *
windows_private_get_cookie_mod(Dirsync_Private *dp, int modtype)
{
    Slapi_Mods *smods = NULL;
    smods = slapi_mods_new();

    slapi_mods_add(smods, modtype,
                   "nsds7DirsyncCookie", dp->dirsync_cookie_len, dp->dirsync_cookie);

    return smods;
}


/*  writes the current cookie into dse.ldif under the replication agreement entry
    returns: ldap result code of the operation. */
int
windows_private_save_dirsync_cookie(const Repl_Agmt *ra)
{
    Dirsync_Private *dp = NULL;
    Slapi_PBlock *pb = NULL;
    Slapi_DN *sdn = NULL;
    int rc = 0;
    Slapi_Mods *mods = NULL;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);


    pb = slapi_pblock_new();

    mods = windows_private_get_cookie_mod(dp, LDAP_MOD_REPLACE);
    sdn = slapi_sdn_dup(agmt_get_dn_byref(ra));

    slapi_modify_internal_set_pb_ext(pb, sdn,
                                     slapi_mods_get_ldapmods_byref(mods), NULL, NULL,
                                     repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0);
    slapi_modify_internal_pb(pb);

    slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);

    if (rc == LDAP_NO_SUCH_ATTRIBUTE) { /* try again, but as an add instead */
        slapi_mods_free(&mods);
        mods = windows_private_get_cookie_mod(dp, LDAP_MOD_ADD);
        slapi_modify_internal_set_pb_ext(pb, sdn,
                                         slapi_mods_get_ldapmods_byref(mods), NULL, NULL,
                                         repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION), 0);
        slapi_modify_internal_pb(pb);

        slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
    }

    slapi_pblock_destroy(pb);
    slapi_mods_free(&mods);
    slapi_sdn_free(&sdn);

    return rc;
}

/*  reads the cookie in dse.ldif to the replication agreement entry
    returns: ldap result code of ldap operation, or
             LDAP_NO_SUCH_ATTRIBUTE. (this is the equilivent of a null cookie) */
int
windows_private_load_dirsync_cookie(const Repl_Agmt *ra)
{
    Dirsync_Private *dp = NULL;
    Slapi_PBlock *pb = NULL;

    Slapi_DN *sdn = NULL;
    int rc = 0;
    Slapi_Entry *entry = NULL;
    Slapi_Attr *attr = NULL;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);


    pb = slapi_pblock_new();
    sdn = slapi_sdn_dup(agmt_get_dn_byref(ra));


    rc = slapi_search_internal_get_entry(sdn, NULL, &entry,
                                         repl_get_plugin_identity(PLUGIN_MULTISUPPLIER_REPLICATION));

    if (rc == 0) {
        rc = slapi_entry_attr_find(entry, type_nsds7DirsyncCookie, &attr);
        if (attr) {
            struct berval **vals;
            rc = slapi_attr_get_bervals_copy(attr, &vals);

            if (vals) {
                dp->dirsync_cookie_len = (int)(vals[0])->bv_len;
                slapi_ch_free_string(&dp->dirsync_cookie);

                dp->dirsync_cookie = (char *)slapi_ch_malloc(dp->dirsync_cookie_len + 1);
                memcpy(dp->dirsync_cookie, (vals[0]->bv_val), (vals[0])->bv_len + 1);
            }

            ber_bvecfree(vals);
            /* we do not free attr */

        } else {
            rc = LDAP_NO_SUCH_ATTRIBUTE;
        }
    }

    if (entry) {
        slapi_entry_free(entry);
    }

    slapi_sdn_free(&sdn);
    slapi_pblock_destroy(pb);

    return rc;
}

/* get returns a pointer to the structure - do not free */
Slapi_Entry *
windows_private_get_raw_entry(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->raw_entry;
}

/* this is passin - windows_private owns the pointer, not a copy */
void
windows_private_set_raw_entry(const Repl_Agmt *ra, Slapi_Entry *e)
{
    Dirsync_Private *dp;

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    /* If the keep raw entry flag is set, just free the passed
     * in entry and leave the current raw entry in place. */
    if (windows_private_get_keep_raw_entry(ra)) {
        slapi_entry_free(e);
    } else {
        slapi_entry_free(dp->raw_entry);
        dp->raw_entry = e;
    }
}

/* Setting keep to 1 will cause the current raw entry to remain, even if
 * windows_private_set_raw_entry() is called.  This behavior will persist
 * until this flag is set back to 0. */
void
windows_private_set_keep_raw_entry(const Repl_Agmt *ra, int keep)
{
    Dirsync_Private *dp;

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    dp->keep_raw_entry = keep;
}

int
windows_private_get_keep_raw_entry(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->keep_raw_entry;
}

void *
windows_private_get_api_cookie(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->api_cookie;
}

void
windows_private_set_api_cookie(Repl_Agmt *ra, void *api_cookie)
{
    Dirsync_Private *dp;

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);
    dp->api_cookie = api_cookie;
}

time_t
windows_private_get_sync_interval(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->sync_interval;
}

void
windows_private_set_sync_interval(Repl_Agmt *ra, char *str)
{
    Dirsync_Private *dp;
    time_t tmpval = 0;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    if (str && (tmpval = (time_t)atol(str))) {
        dp->sync_interval = tmpval;
    }
}

int
windows_private_get_move_action(const Repl_Agmt *ra)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);

    return dp->move_action;
}

void
windows_private_set_move_action(const Repl_Agmt *ra, int value)
{
    Dirsync_Private *dp;

    PR_ASSERT(ra);

    dp = (Dirsync_Private *)agmt_get_priv(ra);
    PR_ASSERT(dp);
    dp->move_action = value;
}

static PRCallOnceType winsync_callOnce = {0, 0, 0};

struct winsync_plugin
{
    struct winsync_plugin *next; /* see PRCList - declare here to avoid lots of casting */
    struct winsync_plugin *prev; /* see PRCList - declare here to avoid lots of casting */
    void **api;                  /* the api - array of function pointers */
    int maxapi;                  /* the max index i.e. the api version */
    int precedence;              /* lower number == higher precedence */
};
static struct winsync_plugin winsync_plugin_list;

#define DECL_WINSYNC_API_IDX_FUNC(theapi, idx, maxidx, thetype, thefunc) \
    thetype thefunc = (theapi && (idx <= maxidx) && theapi[idx]) ? (thetype)theapi[idx] : NULL;

#define WINSYNC_PLUGIN_CALL_PLUGINS_BEGIN(idx, thetype, thefunc)                   \
    struct winsync_plugin *elem;                                                   \
    for (elem = PR_LIST_HEAD(&winsync_plugin_list);                                \
         elem && (elem != &winsync_plugin_list);                                   \
         elem = PR_NEXT_LINK(elem)) {                                              \
        DECL_WINSYNC_API_IDX_FUNC(elem->api, idx, elem->maxapi, thetype, thefunc); \
        if (thefunc) {

#define WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(idx, thetype, thefunc) \
    WINSYNC_PLUGIN_CALL_PLUGINS_BEGIN(idx, thetype, thefunc)            \
    void *cookie = winsync_plugin_cookie_find(ra, elem->api);

#define WINSYNC_PLUGIN_CALL_PLUGINS_END \
    } /* this one matches if thefunc */ \
    } /* this one matches the for loop */

/* this structure is per agreement - to store the cookie per agreement
   for each winsync plugin */
struct winsync_plugin_cookie
{
    struct winsync_plugin_cookie *next; /* see PRCList - declare here to avoid lots of casting */
    struct winsync_plugin_cookie *prev; /* see PRCList - declare here to avoid lots of casting */
    void **api;                         /* the api - array of function pointers */
    void *cookie;                       /* plugin data */
};

static struct winsync_plugin *
new_winsync_plugin(void **theapi, int maxapi, int precedence)
{
    struct winsync_plugin *wpi = (struct winsync_plugin *)slapi_ch_calloc(1, sizeof(struct winsync_plugin));
    wpi->api = theapi;
    wpi->maxapi = maxapi;
    wpi->precedence = precedence;
    return wpi;
}

static struct winsync_plugin *
windows_plugin_find(void **theapi)
{
    struct winsync_plugin *elem = PR_LIST_HEAD(&winsync_plugin_list);
    while (elem && (elem != &winsync_plugin_list)) {
        if (theapi == elem->api) {
            return elem;
        }
        elem = PR_NEXT_LINK(elem);
    }
    return NULL;
}

/* returns 0 for success - 1 means already added - -1 means some error */
static int
windows_plugin_add(void **theapi, int maxapi)
{
    int precedence = WINSYNC_PLUGIN_DEFAULT_PRECEDENCE;
    DECL_WINSYNC_API_IDX_FUNC(theapi, WINSYNC_PLUGIN_PRECEDENCE_CB, maxapi, winsync_plugin_precedence_cb, thefunc);

    if (thefunc) {
        /* supports precedence */
        precedence = (*thefunc)();
    }
    if (PR_CLIST_IS_EMPTY(&winsync_plugin_list)) {
        struct winsync_plugin *wpi = new_winsync_plugin(theapi, maxapi, precedence);
        PR_INSERT_LINK(wpi, &winsync_plugin_list);
        return 0;
    } else if (windows_plugin_find(theapi)) {
        return 1; /* already in list */
    } else {
        struct winsync_plugin *wpi = new_winsync_plugin(theapi, maxapi, precedence);
        struct winsync_plugin *elem = PR_LIST_HEAD(&winsync_plugin_list);
        while (elem && (elem != &winsync_plugin_list)) {
            if (precedence < elem->precedence) {
                PR_INSERT_BEFORE(wpi, elem);
                wpi = NULL; /* owned by list now */
                break;
            }
            elem = PR_NEXT_LINK(elem);
        }
        if (elem && wpi) { /* was not added - precedence too high */
            /* just add to end of list */
            PR_INSERT_BEFORE(wpi, elem);
            wpi = NULL; /* owned by list now */
        }
        /* if we got here and wpi is not NULL we need to free wpi */
        slapi_ch_free((void **)&wpi);
        return 0;
    }
    return -1;
}

static PRStatus
windows_plugin_callonce(void)
{
    char *guids[] = {WINSYNC_v3_0_GUID, WINSYNC_v2_0_GUID, WINSYNC_v1_0_GUID, NULL};
    int maxapis[] = {WINSYNC_PLUGIN_VERSION_3_END, WINSYNC_PLUGIN_VERSION_2_END,
                     WINSYNC_PLUGIN_VERSION_1_END, 0};
    int ii;

    PR_INIT_CLIST(&winsync_plugin_list);
    /* loop through all of the registered winsync plugins - look for them in reverse
       version order (e.g. look for v3 first) - if there are no plugins registered
       for the given version, or we have already registered all plugins for a given
       version, just go to the next lowest version */
    for (ii = 0; guids[ii]; ++ii) {
        char *guid = guids[ii];
        int maxapi = maxapis[ii];
        void ***theapis = NULL;

        if (slapi_apib_get_interface_all(guid, &theapis) || (NULL == theapis)) {
            slapi_log_err(SLAPI_LOG_PLUGIN, windows_repl_plugin_name,
                          "<-- windows_plugin_callonce - No more windows plugin APIs registered "
                          "for GUID [%s] -- end\n",
                          guid);
        } else {
            int idx;
            for (idx = 0; theapis && theapis[idx]; ++idx) {
                if (windows_plugin_add(theapis[idx], maxapi)) {
                    slapi_log_err(SLAPI_LOG_PLUGIN, windows_repl_plugin_name,
                                  "<-- windows_plugin_callonce - Already added windows plugin API "
                                  "[%d][0x%p] for GUID [%s] -- end\n",
                                  idx, theapis[idx], guid);
                }
            }
        }
        slapi_ch_free((void **)&theapis);
    }
    return PR_SUCCESS;
}

static struct winsync_plugin_cookie *
new_winsync_plugin_cookie(void **theapi, void *cookie)
{
    struct winsync_plugin_cookie *wpc = (struct winsync_plugin_cookie *)slapi_ch_calloc(1, sizeof(struct winsync_plugin_cookie));
    wpc->api = theapi;
    wpc->cookie = cookie;
    return wpc;
}

static void *
winsync_plugin_cookie_find(const Repl_Agmt *ra, void **theapi)
{
    if (ra) {
        struct winsync_plugin_cookie *list = (struct winsync_plugin_cookie *)windows_private_get_api_cookie(ra);
        if (list) {
            struct winsync_plugin_cookie *elem = PR_LIST_HEAD(list);
            while (elem && (elem != list)) {
                if (theapi == elem->api) {
                    return elem->cookie;
                }
                elem = PR_NEXT_LINK(elem);
            }
        }
    }
    return NULL;
}

static void
winsync_plugin_cookie_add(struct winsync_plugin_cookie **list, void **theapi, void *cookie)
{
    struct winsync_plugin_cookie *elem = NULL;
    if (!*list) {
        *list = new_winsync_plugin_cookie(NULL, NULL);
        PR_INIT_CLIST(*list);
    }
    elem = new_winsync_plugin_cookie(theapi, cookie);
    PR_INSERT_BEFORE(elem, *list);
    return;
}

void
windows_plugin_init(Repl_Agmt *ra)
{
    struct winsync_plugin_cookie *list = NULL;
    void *cookie = NULL;

    slapi_log_err(SLAPI_LOG_PLUGIN, windows_repl_plugin_name, "windows_plugin_init - Begin\n");

    if (PR_CallOnce(&winsync_callOnce, windows_plugin_callonce)) {
        PRErrorCode prerr = PR_GetError();
        slapi_log_err(SLAPI_LOG_ERR, windows_repl_plugin_name, "windows_plugin_init - "
                                                               "Cannot initialize plugin: %d:%s\n",
                      prerr,
                      slapi_pr_strerror(prerr));
        return;
    }

    /* call each plugin init function in turn - store the returned cookie
       indexed by the api */
    {
        WINSYNC_PLUGIN_CALL_PLUGINS_BEGIN(WINSYNC_PLUGIN_INIT_CB, winsync_plugin_init_cb, thefunc)
        cookie = (*thefunc)(windows_private_get_directory_subtree(ra),
                            windows_private_get_windows_subtree(ra));
        if (cookie) {
            winsync_plugin_cookie_add(&list, elem->api, cookie);
        }
        WINSYNC_PLUGIN_CALL_PLUGINS_END
    }

    windows_private_set_api_cookie(ra, list);

    slapi_log_err(SLAPI_LOG_PLUGIN, windows_repl_plugin_name, "<-- windows_plugin_init - End\n");
    return;
}

void
windows_plugin_cleanup_agmt(Repl_Agmt *ra)
{
    struct winsync_plugin_cookie *list = (struct winsync_plugin_cookie *)windows_private_get_api_cookie(ra);
    struct winsync_plugin_cookie *elem = NULL;

    while (list && !PR_CLIST_IS_EMPTY(list)) {
        elem = PR_LIST_HEAD(list);
        PR_REMOVE_LINK(elem);
        slapi_ch_free((void **)&elem);
    }
    slapi_ch_free((void **)&list);
    windows_private_set_api_cookie(ra, NULL);
    return;
}

void
winsync_plugin_call_dirsync_search_params_cb(const Repl_Agmt *ra, const char *agmt_dn, char **base, int *scope, char **filter, char ***attrs, LDAPControl ***serverctrls)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_DIRSYNC_SEARCH_CB, winsync_search_params_cb, thefunc)
    (*thefunc)(cookie, agmt_dn, base, scope, filter, attrs, serverctrls);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ad_search_cb(const Repl_Agmt *ra, const char *agmt_dn, char **base, int *scope, char **filter, char ***attrs, LDAPControl ***serverctrls)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_AD_SEARCH_CB, winsync_search_params_cb, thefunc)
    (*thefunc)(cookie, agmt_dn, base, scope, filter, attrs, serverctrls);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ds_search_entry_cb(const Repl_Agmt *ra, const char *agmt_dn, char **base, int *scope, char **filter, char ***attrs, LDAPControl ***serverctrls)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_DS_SEARCH_ENTRY_CB, winsync_search_params_cb, thefunc)
    (*thefunc)(cookie, agmt_dn, base, scope, filter, attrs, serverctrls);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ds_search_all_cb(const Repl_Agmt *ra, const char *agmt_dn, char **base, int *scope, char **filter, char ***attrs, LDAPControl ***serverctrls)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_DS_SEARCH_ALL_CB, winsync_search_params_cb, thefunc)
    (*thefunc)(cookie, agmt_dn, base, scope, filter, attrs, serverctrls);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ad_mod_user_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, Slapi_Mods *smods, int *do_modify)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_AD_MOD_USER_CB, winsync_pre_mod_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, smods, do_modify);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ad_mod_group_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, Slapi_Mods *smods, int *do_modify)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_AD_MOD_GROUP_CB, winsync_pre_mod_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, smods, do_modify);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ds_mod_user_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, Slapi_Mods *smods, int *do_modify)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_DS_MOD_USER_CB, winsync_pre_mod_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, smods, do_modify);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ds_mod_group_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, Slapi_Mods *smods, int *do_modify)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_DS_MOD_GROUP_CB, winsync_pre_mod_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, smods, do_modify);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ds_add_user_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_DS_ADD_USER_CB, winsync_pre_add_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ds_add_group_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_DS_ADD_GROUP_CB, winsync_pre_add_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_get_new_ds_user_dn_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, char **new_dn_string, const Slapi_DN *ds_suffix, const Slapi_DN *ad_suffix)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_GET_NEW_DS_USER_DN_CB, winsync_get_new_dn_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, new_dn_string, ds_suffix, ad_suffix);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_get_new_ds_group_dn_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, char **new_dn_string, const Slapi_DN *ds_suffix, const Slapi_DN *ad_suffix)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_GET_NEW_DS_GROUP_DN_CB, winsync_get_new_dn_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, new_dn_string, ds_suffix, ad_suffix);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ad_mod_user_mods_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, const Slapi_DN *local_dn, const Slapi_Entry *ds_entry, LDAPMod *const *origmods, Slapi_DN *remote_dn, LDAPMod ***modstosend)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_AD_MOD_USER_MODS_CB, winsync_pre_ad_mod_mods_cb, thefunc)
    (*thefunc)(cookie, rawentry, local_dn, ds_entry, origmods, remote_dn, modstosend);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ad_mod_group_mods_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, const Slapi_DN *local_dn, const Slapi_Entry *ds_entry, LDAPMod *const *origmods, Slapi_DN *remote_dn, LDAPMod ***modstosend)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_AD_MOD_GROUP_MODS_CB, winsync_pre_ad_mod_mods_cb, thefunc)
    (*thefunc)(cookie, rawentry, local_dn, ds_entry, origmods, remote_dn, modstosend);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

int
winsync_plugin_call_can_add_entry_to_ad_cb(const Repl_Agmt *ra, const Slapi_Entry *local_entry, const Slapi_DN *remote_dn)
{
    int canadd = 1;
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_CAN_ADD_ENTRY_TO_AD_CB, winsync_can_add_to_ad_cb, thefunc)
    if (canadd) {
        canadd = (*thefunc)(cookie, local_entry, remote_dn);
    }
    WINSYNC_PLUGIN_CALL_PLUGINS_END;
    return canadd;
}

void
winsync_plugin_call_begin_update_cb(const Repl_Agmt *ra, const Slapi_DN *ds_subtree, const Slapi_DN *ad_subtree, int is_total)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_BEGIN_UPDATE_CB, winsync_plugin_update_cb, thefunc)
    (*thefunc)(cookie, ds_subtree, ad_subtree, is_total);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_end_update_cb(const Repl_Agmt *ra, const Slapi_DN *ds_subtree, const Slapi_DN *ad_subtree, int is_total)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_END_UPDATE_CB, winsync_plugin_update_cb, thefunc)
    (*thefunc)(cookie, ds_subtree, ad_subtree, is_total);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_destroy_agmt_cb(const Repl_Agmt *ra,
                                    const Slapi_DN *ds_subtree,
                                    const Slapi_DN *ad_subtree)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_DESTROY_AGMT_CB, winsync_plugin_destroy_agmt_cb, thefunc)
    (*thefunc)(cookie, ds_subtree, ad_subtree);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ad_mod_user_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, Slapi_Mods *smods, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_AD_MOD_USER_CB, winsync_post_mod_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, smods, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ad_mod_group_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, Slapi_Mods *smods, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_AD_MOD_GROUP_CB, winsync_post_mod_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, smods, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ds_mod_user_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, Slapi_Mods *smods, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_DS_MOD_USER_CB, winsync_post_mod_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, smods, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ds_mod_group_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, Slapi_Mods *smods, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_DS_MOD_GROUP_CB, winsync_post_mod_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, smods, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ds_add_user_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_DS_ADD_USER_CB, winsync_post_add_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ds_add_group_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_DS_ADD_GROUP_CB, winsync_post_add_cb, thefunc)
    (*thefunc)(cookie, rawentry, ad_entry, ds_entry, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ad_add_user_cb(const Repl_Agmt *ra, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_AD_ADD_USER_CB, winsync_pre_ad_add_cb, thefunc)
    (*thefunc)(cookie, ad_entry, ds_entry);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_pre_ad_add_group_cb(const Repl_Agmt *ra, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_PRE_AD_ADD_GROUP_CB, winsync_pre_ad_add_cb, thefunc)
    (*thefunc)(cookie, ad_entry, ds_entry);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ad_add_user_cb(const Repl_Agmt *ra, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_AD_ADD_USER_CB, winsync_post_ad_add_cb, thefunc)
    (*thefunc)(cookie, ad_entry, ds_entry, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ad_add_group_cb(const Repl_Agmt *ra, Slapi_Entry *ad_entry, Slapi_Entry *ds_entry, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_AD_ADD_GROUP_CB, winsync_post_ad_add_cb, thefunc)
    (*thefunc)(cookie, ad_entry, ds_entry, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ad_mod_user_mods_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, const Slapi_DN *local_dn, const Slapi_Entry *ds_entry, LDAPMod *const *origmods, Slapi_DN *remote_dn, LDAPMod **modstosend, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_AD_MOD_USER_MODS_CB, winsync_post_ad_mod_mods_cb, thefunc)
    (*thefunc)(cookie, rawentry, local_dn, ds_entry, origmods, remote_dn, modstosend, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}

void
winsync_plugin_call_post_ad_mod_group_mods_cb(const Repl_Agmt *ra, const Slapi_Entry *rawentry, const Slapi_DN *local_dn, const Slapi_Entry *ds_entry, LDAPMod *const *origmods, Slapi_DN *remote_dn, LDAPMod **modstosend, int *result)
{
    WINSYNC_PLUGIN_CALL_PLUGINS_COOKIE_BEGIN(WINSYNC_PLUGIN_POST_AD_MOD_GROUP_MODS_CB, winsync_post_ad_mod_mods_cb, thefunc)
    (*thefunc)(cookie, rawentry, local_dn, ds_entry, origmods, remote_dn, modstosend, result);
    WINSYNC_PLUGIN_CALL_PLUGINS_END;

    return;
}


/*
  The following are sample code stubs to show how to implement
  a plugin which uses this api
*/

#define WINSYNC_SAMPLE_CODE
#ifdef WINSYNC_SAMPLE_CODE

#include "slapi-plugin.h"
#include "winsync-plugin.h"

static char *test_winsync_plugin_name = "test_winsync_api";

static void *
test_winsync_api_init(const Slapi_DN *ds_subtree, const Slapi_DN *ad_subtree)
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_init [%s] [%s] -- begin\n",
                  slapi_sdn_get_dn(ds_subtree),
                  slapi_sdn_get_dn(ad_subtree));

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_init -- end\n");

    return NULL;
}

static void
test_winsync_dirsync_search_params_cb(void *cbdata __attribute__((unused)), const char *agmt_dn __attribute__((unused)), char **base __attribute__((unused)), int *scope __attribute__((unused)), char **filter __attribute__((unused)), char ***attrs __attribute__((unused)), LDAPControl ***serverctrls __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_dirsync_search_params_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_dirsync_search_params_cb -- end\n");

    return;
}

/* called before searching for a single entry from AD - agmt_dn will be NULL */
static void
test_winsync_pre_ad_search_cb(void *cbdata __attribute__((unused)), const char *agmt_dn __attribute__((unused)), char **base __attribute__((unused)), int *scope __attribute__((unused)), char **filter __attribute__((unused)), char ***attrs __attribute__((unused)), LDAPControl ***serverctrls __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ad_search_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ad_search_cb -- end\n");

    return;
}

/* called before an internal search to get a single DS entry - agmt_dn will be NULL */
static void
test_winsync_pre_ds_search_entry_cb(void *cbdata __attribute__((unused)), const char *agmt_dn __attribute__((unused)), char **base __attribute__((unused)), int *scope __attribute__((unused)), char **filter __attribute__((unused)), char ***attrs __attribute__((unused)), LDAPControl ***serverctrls __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ds_search_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ds_search_cb -- end\n");

    return;
}

/* called before the total update to get all entries from the DS to sync to AD */
static void
test_winsync_pre_ds_search_all_cb(void *cbdata __attribute__((unused)), const char *agmt_dn __attribute__((unused)), char **base __attribute__((unused)), int *scope __attribute__((unused)), char **filter, char ***attrs __attribute__((unused)), LDAPControl ***serverctrls __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ds_search_all_cb -- orig filter [%s] -- begin\n",
                  ((filter && *filter) ? *filter : "NULL"));

#ifdef THIS_IS_JUST_AN_EXAMPLE
    if (filter) {
        /* We only want to grab users from the ds side - no groups */
        slapi_ch_free_string(filter);
        /* maybe use ntUniqueId=* - only get users that have already been
           synced with AD already - ntUniqueId and ntUserDomainId are
           indexed for equality only - need to add presence? */
        *filter = slapi_ch_strdup("(&(objectclass=ntuser)(ntUserDomainId=*))");
        slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                        "--> test_winsync_pre_ds_search_all_cb -- new filter [%s]\n",
                        *filter ? *filter : "NULL"));
    }
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ds_search_all_cb -- end\n");

    return;
}

static void
test_winsync_pre_ad_mod_user_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Mods *smods __attribute__((unused)), int *do_modify __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ad_mod_user_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ad_mod_user_cb -- end\n");

    return;
}

static void
test_winsync_pre_ad_mod_group_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Mods *smods __attribute__((unused)), int *do_modify __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ad_mod_group_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ad_mod_group_cb -- end\n");

    return;
}

static void
test_winsync_pre_ds_mod_user_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Mods *smods __attribute__((unused)), int *do_modify __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ds_mod_user_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ds_mod_user_cb -- end\n");

    return;
}

static void
test_winsync_pre_ds_mod_group_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Mods *smods __attribute__((unused)), int *do_modify __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ds_mod_group_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ds_mod_group_cb -- end\n");

    return;
}

static void
test_winsync_pre_ds_add_user_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ds_add_user_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ds_add_user_cb -- end\n");

    return;
}

static void
test_winsync_pre_ds_add_group_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ds_add_group_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ds_add_group_cb -- end\n");

    return;
}

static void
test_winsync_get_new_ds_user_dn_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), char **new_dn_string, const Slapi_DN *ds_suffix __attribute__((unused)), const Slapi_DN *ad_suffix __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_get_new_ds_user_dn_cb -- old dn [%s] -- begin\n",
                  *new_dn_string);

#ifdef THIS_IS_JUST_AN_EXAMPLE
    char **rdns = slapi_ldap_explode_dn(*new_dn_string, 0);
    if (!rdns || !rdns[0]) {
        slapi_ldap_value_free(rdns);
        return;
    }

    slapi_ch_free_string(new_dn_string);
    *new_dn_string = PR_smprintf("%s,%s", rdns[0], slapi_sdn_get_dn(ds_suffix));
    slapi_ldap_value_free(rdns);
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_get_new_ds_user_dn_cb -- new dn [%s] -- end\n",
                  *new_dn_string);

    return;
}

static void
test_winsync_get_new_ds_group_dn_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), char **new_dn_string __attribute__((unused)), const Slapi_DN *ds_suffix __attribute__((unused)), const Slapi_DN *ad_suffix __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_get_new_ds_group_dn_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_get_new_ds_group_dn_cb -- end\n");

    return;
}

static void
test_winsync_pre_ad_mod_user_mods_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), const Slapi_Entry *ds_entry __attribute__((unused)), const Slapi_DN *local_dn __attribute__((unused)), LDAPMod *const *origmods __attribute__((unused)), Slapi_DN *remote_dn __attribute__((unused)), LDAPMod ***modstosend __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ad_mod_user_mods_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ad_mod_user_mods_cb -- end\n");

    return;
}

static void
test_winsync_pre_ad_mod_group_mods_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), const Slapi_Entry *ds_entry __attribute__((unused)), const Slapi_DN *local_dn __attribute__((unused)), LDAPMod *const *origmods __attribute__((unused)), Slapi_DN *remote_dn __attribute__((unused)), LDAPMod ***modstosend __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ad_mod_group_mods_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ad_mod_group_mods_cb -- end\n");

    return;
}

static int
test_winsync_can_add_entry_to_ad_cb(void *cbdata __attribute__((unused)), const Slapi_Entry *local_entry __attribute__((unused)), const Slapi_DN *remote_dn __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_can_add_entry_to_ad_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_can_add_entry_to_ad_cb -- end\n");

    /*    return 0;*/ /* false - do not allow entries to be added to ad */
    return 1;         /* true - allow entries to be added to ad */
}

static void
test_winsync_begin_update_cb(void *cbdata __attribute__((unused)), const Slapi_DN *ds_subtree __attribute__((unused)), const Slapi_DN *ad_subtree __attribute__((unused)), int is_total __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_begin_update_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_begin_update_cb -- end\n");

    return;
}

static void
test_winsync_end_update_cb(void *cbdata __attribute__((unused)), const Slapi_DN *ds_subtree __attribute__((unused)), const Slapi_DN *ad_subtree __attribute__((unused)), int is_total __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_end_update_cb -- begin\n");

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_end_update_cb -- end\n");

    return;
}

static void
test_winsync_destroy_agmt_cb(void *cbdata __attribute__((unused)), const Slapi_DN *ds_subtree __attribute__((unused)), const Slapi_DN *ad_subtree __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_destroy_agmt_cb -- begin\n");

    /* free(cbdata); */

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_destroy_agmt_cb -- end\n");

    return;
}

static void
test_winsync_post_ad_mod_user_cb(void *cookie __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Mods *smods __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ad_mod_user_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of modifying AD entry [%s] was [%d:%s]\n",
                  slapi_entry_get_dn(ad_entry), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ad_mod_user_cb -- end\n");

    return;
}

static void
test_winsync_post_ad_mod_group_cb(void *cookie __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Mods *smods __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ad_mod_group_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of modifying AD entry [%s] was [%d:%s]\n",
                  slapi_entry_get_dn(ad_entry), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ad_mod_group_cb -- end\n");

    return;
}

static void
test_winsync_post_ds_mod_user_cb(void *cookie __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Mods *smods __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ds_mod_user_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of modifying DS entry [%s] was [%d:%s]\n",
                  slapi_entry_get_dn(ds_entry), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ds_mod_user_cb -- end\n");

    return;
}

static void
test_winsync_post_ds_mod_group_cb(void *cookie __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Mods *smods __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ds_mod_group_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of modifying DS entry [%s] was [%d:%s]\n",
                  slapi_entry_get_dn(ds_entry), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ds_mod_group_cb -- end\n");

    return;
}

static void
test_winsync_post_ds_add_user_cb(void *cookie __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ds_add_user_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of adding DS entry [%s] was [%d:%s]\n",
                  slapi_entry_get_dn(ds_entry), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ds_add_user_cb -- end\n");

    return;
}

static void
test_winsync_post_ds_add_group_cb(void *cookie __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ds_add_group_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of adding DS entry [%s] was [%d:%s]\n",
                  slapi_entry_get_dn(ds_entry), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ds_add_group_cb -- end\n");

    return;
}

static void
test_winsync_pre_ad_add_user_cb(void *cookie __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ad_add_user_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Adding AD entry [%s] from add of DS entry [%s]\n",
                  slapi_entry_get_dn(ad_entry), slapi_entry_get_dn(ds_entry));
/* make modifications to ad_entry here */
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ad_add_user_cb -- end\n");

    return;
}

static void
test_winsync_pre_ad_add_group_cb(void *cookie __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_pre_ad_add_group_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Adding AD entry [%s] from add of DS entry [%s]\n",
                  slapi_entry_get_dn(ad_entry), slapi_entry_get_dn(ds_entry));
/* make modifications to ad_entry here */
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_pre_ad_add_group_cb -- end\n");

    return;
}

static void
test_winsync_post_ad_add_user_cb(void *cookie __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ad_add_user_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of adding AD entry [%s] was [%d:%s]\n",
                  slapi_entry_get_dn(ad_entry), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ad_add_user_cb -- end\n");

    return;
}

static void
test_winsync_post_ad_add_group_cb(void *cookie __attribute__((unused)), Slapi_Entry *ds_entry __attribute__((unused)), Slapi_Entry *ad_entry __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ad_add_group_cb -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of adding AD entry [%s] was [%d:%s]\n",
                  slapi_entry_get_dn(ad_entry), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ad_add_group_cb -- end\n");

    return;
}

static void
test_winsync_post_ad_mod_user_mods_cb(void *cookie __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), const Slapi_DN *local_dn __attribute__((unused)), const Slapi_Entry *ds_entry __attribute__((unused)), LDAPMod *const *origmods __attribute__((unused)), Slapi_DN *remote_dn __attribute__((unused)), LDAPMod ***modstosend __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ad_mod_user_mods_cb  -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of modifying AD entry [%s] was [%d:%s]\n",
                  slapi_sdn_get_dn(remote_dn), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ad_mod_user_mods_cb -- end\n");

    return;
}

static void
test_winsync_post_ad_mod_group_mods_cb(void *cookie __attribute__((unused)), const Slapi_Entry *rawentry __attribute__((unused)), const Slapi_DN *local_dn __attribute__((unused)), const Slapi_Entry *ds_entry __attribute__((unused)), LDAPMod *const *origmods __attribute__((unused)), Slapi_DN *remote_dn __attribute__((unused)), LDAPMod ***modstosend __attribute__((unused)), int *result __attribute__((unused)))
{
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "--> test_winsync_post_ad_mod_group_mods_cb  -- begin\n");

#ifdef THIS_IS_JUST_AN_EXAMPLE
    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "Result of modifying AD entry [%s] was [%d:%s]\n",
                  slapi_sdn_get_dn(remote_dn), *result, ldap_err2string(*result));
#endif

    slapi_log_err(SLAPI_LOG_PLUGIN, test_winsync_plugin_name,
                  "<-- test_winsync_post_ad_mod_group_mods_cb -- end\n");

    return;
}

static int
test_winsync_precedence(void)
{
    return 99;
}

/**
 * Plugin identifiers
 */
static Slapi_PluginDesc test_winsync_pdesc = {
    "test-winsync-plugin",
    VENDOR,
    DS_PACKAGE_VERSION,
    "test winsync plugin"};

static Slapi_ComponentId *test_winsync_plugin_id = NULL;

#ifdef TEST_V1_WINSYNC_API
static void *test_winsync_api_v1[] = {
    NULL, /* reserved for api broker use, must be zero */
    test_winsync_api_init,
    test_winsync_dirsync_search_params_cb,
    test_winsync_pre_ad_search_cb,
    test_winsync_pre_ds_search_entry_cb,
    test_winsync_pre_ds_search_all_cb,
    test_winsync_pre_ad_mod_user_cb,
    test_winsync_pre_ad_mod_group_cb,
    test_winsync_pre_ds_mod_user_cb,
    test_winsync_pre_ds_mod_group_cb,
    test_winsync_pre_ds_add_user_cb,
    test_winsync_pre_ds_add_group_cb,
    test_winsync_get_new_ds_user_dn_cb,
    test_winsync_get_new_ds_group_dn_cb,
    test_winsync_pre_ad_mod_user_mods_cb,
    test_winsync_pre_ad_mod_group_mods_cb,
    test_winsync_can_add_entry_to_ad_cb,
    test_winsync_begin_update_cb,
    test_winsync_end_update_cb,
    test_winsync_destroy_agmt_cb};
#endif /* TEST_V1_WINSYNC_API */

#ifdef TEST_V2_WINSYNC_API
static void *test_winsync_api_v2[] = {
    NULL, /* reserved for api broker use, must be zero */
    test_winsync_api_init,
    test_winsync_dirsync_search_params_cb,
    test_winsync_pre_ad_search_cb,
    test_winsync_pre_ds_search_entry_cb,
    test_winsync_pre_ds_search_all_cb,
    test_winsync_pre_ad_mod_user_cb,
    test_winsync_pre_ad_mod_group_cb,
    test_winsync_pre_ds_mod_user_cb,
    test_winsync_pre_ds_mod_group_cb,
    test_winsync_pre_ds_add_user_cb,
    test_winsync_pre_ds_add_group_cb,
    test_winsync_get_new_ds_user_dn_cb,
    test_winsync_get_new_ds_group_dn_cb,
    test_winsync_pre_ad_mod_user_mods_cb,
    test_winsync_pre_ad_mod_group_mods_cb,
    test_winsync_can_add_entry_to_ad_cb,
    test_winsync_begin_update_cb,
    test_winsync_end_update_cb,
    test_winsync_destroy_agmt_cb,
    test_winsync_post_ad_mod_user_cb,
    test_winsync_post_ad_mod_group_cb,
    test_winsync_post_ds_mod_user_cb,
    test_winsync_post_ds_mod_group_cb,
    test_winsync_post_ds_add_user_cb,
    test_winsync_post_ds_add_group_cb,
    test_winsync_pre_ad_add_user_cb,
    test_winsync_pre_ad_add_group_cb,
    test_winsync_post_ad_add_user_cb,
    test_winsync_post_ad_add_group_cb,
    test_winsync_post_ad_mod_user_mods_cb,
    test_winsync_post_ad_mod_group_mods_cb};
#endif /* TEST_V2_WINSYNC_API */

static void *test_winsync_api_v3[] = {
    NULL, /* reserved for api broker use, must be zero */
    test_winsync_api_init,
    test_winsync_dirsync_search_params_cb,
    test_winsync_pre_ad_search_cb,
    test_winsync_pre_ds_search_entry_cb,
    test_winsync_pre_ds_search_all_cb,
    test_winsync_pre_ad_mod_user_cb,
    test_winsync_pre_ad_mod_group_cb,
    test_winsync_pre_ds_mod_user_cb,
    test_winsync_pre_ds_mod_group_cb,
    test_winsync_pre_ds_add_user_cb,
    test_winsync_pre_ds_add_group_cb,
    test_winsync_get_new_ds_user_dn_cb,
    test_winsync_get_new_ds_group_dn_cb,
    test_winsync_pre_ad_mod_user_mods_cb,
    test_winsync_pre_ad_mod_group_mods_cb,
    test_winsync_can_add_entry_to_ad_cb,
    test_winsync_begin_update_cb,
    test_winsync_end_update_cb,
    test_winsync_destroy_agmt_cb,
    test_winsync_post_ad_mod_user_cb,
    test_winsync_post_ad_mod_group_cb,
    test_winsync_post_ds_mod_user_cb,
    test_winsync_post_ds_mod_group_cb,
    test_winsync_post_ds_add_user_cb,
    test_winsync_post_ds_add_group_cb,
    test_winsync_pre_ad_add_user_cb,
    test_winsync_pre_ad_add_group_cb,
    test_winsync_post_ad_add_user_cb,
    test_winsync_post_ad_add_group_cb,
    test_winsync_post_ad_mod_user_mods_cb,
    test_winsync_post_ad_mod_group_mods_cb,
    test_winsync_precedence};

static int
test_winsync_plugin_start(Slapi_PBlock *pb __attribute__((unused)))
{
    if (slapi_apib_register(WINSYNC_v3_0_GUID, test_winsync_api_v3)) {
        slapi_log_err(SLAPI_LOG_ERR, test_winsync_plugin_name,
                      "test_winsync_plugin_start - Failed to register winsync api -- end\n");
        return -1;
    }
    return 0;
}

static int
test_winsync_plugin_close(Slapi_PBlock *pb __attribute__((unused)))
{
    slapi_apib_unregister(WINSYNC_v3_0_GUID);
    return 0;
}

/* this is the slapi plugin init function,
   not the one used by the winsync api
*/
int
test_winsync_plugin_init(Slapi_PBlock *pb)
{
    if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION,
                         SLAPI_PLUGIN_VERSION_01) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN,
                         (void *)test_winsync_plugin_start) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN,
                         (void *)test_winsync_plugin_close) != 0 ||
        slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION,
                         (void *)&test_winsync_pdesc) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, test_winsync_plugin_name,
                      "<-- test_winsync_plugin_init -- failed to register plugin -- end\n");
        return -1;
    }

    /* Retrieve and save the plugin identity to later pass to
       internal operations */
    if (slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &test_winsync_plugin_id) != 0) {
        slapi_log_err(SLAPI_LOG_ERR, test_winsync_plugin_name,
                      "test_winsync_plugin_init - Failed to retrieve plugin identity -- end\n");
        return -1;
    }

    return 0;
}

/*
dn: cn=Test Winsync API,cn=plugins,cn=config
objectclass: top
objectclass: nsSlapdPlugin
objectclass: extensibleObject
cn: Test Winsync API
nsslapd-pluginpath: libtestwinsync-plugin
nsslapd-plugininitfunc: test_winsync_plugin_init
nsslapd-plugintype: preoperation
nsslapd-pluginenabled: on
nsslapd-plugin-depends-on-type: database
nsslapd-pluginDescription: Test Winsync
nsslapd-pluginVendor: 389 project
nsslapd-pluginId: test-winsync
nsslapd-pluginVersion: 0.9
*/

#endif /* WINSYNC_SAMPLE_CODE */

/* #define WINSYNC_TEST_IPA */
#ifdef WINSYNC_TEST_IPA

#include "ipa-winsync.c"
#include "ipa-winsync-config.c"

#endif
