/** Author: Carsten Grzemba grzemba@contac-dt.de>
 *
 * Copyright (C) 2011 contac Datentechnik GmbH
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 only
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

 $Id: posix-group-func.c 28 2011-05-13 14:35:29Z grzemba $
 */
#include "slapi-plugin.h"

#include <string.h>
#include <nspr.h>
#include "posix-wsp-ident.h"

Slapi_Value **
valueset_get_valuearray(const Slapi_ValueSet *vs); /* stolen from proto-slap.h */
static PRMonitor *memberuid_operation_lock = 0;

void
memberUidLock()
{
    PR_EnterMonitor(memberuid_operation_lock);
}

void
memberUidUnlock()
{
    PR_ExitMonitor(memberuid_operation_lock);
}

int
memberUidLockInit()
{
    return (memberuid_operation_lock = PR_NewMonitor()) != NULL;
}

/* search the user with DN udn and returns uid*/
char *
searchUid(const char *udn)
{
    Slapi_PBlock *int_search_pb = slapi_pblock_new();
    Slapi_Entry **entries = NULL;
    char *attrs[] = { "uid", NULL };
    char *uid = NULL;

    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME, "search Uid: search %s\n", udn);

    slapi_search_internal_set_pb(int_search_pb, udn, LDAP_SCOPE_BASE,
                                 "(|(objectclass=posixAccount)(objectclass=ldapsubentry))", attrs,
                                 0 /* attrsonly */, NULL /* controls */, NULL /* uniqueid */,
                                 posix_winsync_get_plugin_identity(), 0 /* actions */);
    if (slapi_search_internal_pb(int_search_pb)) {
        /* get result and log an error */
        int res = 0;
        slapi_pblock_get(int_search_pb, SLAPI_PLUGIN_INTOP_RESULT, &res);
        slapi_log_error(SLAPI_LOG_FATAL, POSIX_WINSYNC_PLUGIN_NAME,
                        "searchUid: error searching for uid: %d", res);
    } else {
        slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME, "searchUid: searched %s\n",
                        udn);
        slapi_pblock_get(int_search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries);
        if (NULL != entries && NULL != entries[0]) {
            Slapi_Attr *attr = NULL;
            Slapi_Value *v = NULL;

            if (slapi_entry_attr_find(entries[0], "uid", &attr) == 0) {
                slapi_attr_first_value(attr, &v);
                uid = slapi_ch_strdup(slapi_value_get_string(v));
                slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                "searchUid: return uid %s\n", uid);
                /* slapi_value_free(&v); */
            } else {
                slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                "searchUid: uid in %s not found\n", udn);
            }
            slapi_free_search_results_internal(int_search_pb);
            slapi_pblock_destroy(int_search_pb);
            if (uid && posix_winsync_config_get_lowercase()) {
                return slapi_dn_ignore_case(uid);
            }
            return uid;
        }
    }
    slapi_free_search_results_internal(int_search_pb);
    slapi_pblock_destroy(int_search_pb);
    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                    "searchUid: posix user %s not found\n", udn);
    return NULL;
}

int
dn_in_set(const char* uid, char **uids)
{
    int i;
    Slapi_DN *sdn_uid = NULL;
    Slapi_DN *sdn_ul = NULL;

    if (uids == NULL || uid == NULL)
        return false;

    sdn_uid = slapi_sdn_new_dn_byval(uid);
    sdn_ul = slapi_sdn_new();

    for (i = 0; uids[i]; i++) {
        slapi_sdn_set_dn_byref(sdn_ul, uids[i]);
        if (slapi_sdn_compare(sdn_uid, sdn_ul) == 0) {
            slapi_sdn_free(&sdn_ul);
            slapi_sdn_free(&sdn_uid);
            return true;
        }
        slapi_sdn_done(sdn_ul);
    }
    slapi_sdn_free(&sdn_ul);
    slapi_sdn_free(&sdn_uid);
    return false;
}

int
uid_in_set(const char* uid, char **uids)
{
    int i;

    if (uid == NULL)
        return false;
    for (i = 0; uids != NULL && uids[i] != NULL; i++) {
        Slapi_RDN *i_rdn = NULL;
        char *i_uid = NULL;
        char *t = NULL;

        slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME, "uid_in_set: comp %s %s \n",
                        uid, uids[i]);
        i_rdn = slapi_rdn_new_dn(uids[i]);
        if (slapi_rdn_get_first(i_rdn, &t, &i_uid) == 1) {
            if (strncasecmp(uid, i_uid, 256) == 0) {
                slapi_rdn_free(&i_rdn);
                return true;
            }
        }
        slapi_rdn_free(&i_rdn);
    }
    return false;
}

/* return 1 if smods already has the given mod - 0 otherwise */
static int
smods_has_mod(Slapi_Mods *smods, int modtype, const char *type, const char *val)
{
    int rc = 0;
    Slapi_Mod *smod = slapi_mod_new(), *smodp = NULL;

    for (smodp = slapi_mods_get_first_smod(smods, smod);
         (rc == 0) && smods && (smodp != NULL);
         smodp = slapi_mods_get_next_smod(smods, smod)) {
        if (slapi_attr_types_equivalent(slapi_mod_get_type(smod), type)
            && ((slapi_mod_get_operation(smod) | LDAP_MOD_BVALUES) == (modtype | LDAP_MOD_BVALUES))) {
            /* type and op are equal - see if val is in the mod's list of values */
            Slapi_Value *sval = slapi_value_new_string((char *) val);
            Slapi_Attr *attr = slapi_attr_new();
            struct berval *bvp = NULL;

            slapi_attr_init(attr, type);
            for (bvp = slapi_mod_get_first_value(smodp); (rc == 0) && (bvp != NULL);
                 bvp = slapi_mod_get_next_value(smodp)) {
                Slapi_Value *modval = slapi_value_new_berval(bvp);

                rc = (slapi_value_compare(attr, sval, modval) == 0);
                slapi_value_free(&modval);
            }
            slapi_value_free(&sval);
            slapi_attr_free(&attr);
        }
    }
    slapi_mod_free(&smod);
    return rc;
}

int
isPosixGroup(Slapi_Entry *entry)
{
    int rc = 0;
    int i;
    Slapi_Attr  *obj_attr = NULL;
    Slapi_Value *value    = NULL;

    rc = slapi_entry_attr_find(entry, "objectclass", &obj_attr);

    if (rc != 0) {
        return 0; /* Doesn't have any objectclasses */
    }

    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                    "add/mod-GroupMembership scan objectclasses\n");

    for (
        i = slapi_attr_first_value(obj_attr, &value);
        i != -1;
        i = slapi_attr_next_value(obj_attr, i, &value)
    ) {
        const char *oc = NULL;
        oc = slapi_value_get_string(value);
        if (strncasecmp(oc, "posixGroup", 11) == 0) {
            return 1; /* Entry has objectclass posixGroup */
        }
    }

    return 0; /* Doesn't have objectclass "posixGroup" */
}

int
modGroupMembership(Slapi_Entry *entry, Slapi_Mods *smods, int *do_modify)
{
    int rc = 0;

    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME, "modGroupMembership: ==>\n");

    if (!isPosixGroup(entry)) {
        return 0;
    }

    Slapi_Mod *smod = NULL;
    Slapi_Mod *nextMod = slapi_mod_new();
    int del_mod = 0; /* Bool: was there a delete mod? */
    char **smod_adduids = NULL;
    char **smod_deluids = NULL;

    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                    "modGroupMembership: posixGroup -> look for uniquemember\n");
    if (slapi_is_loglevel_set(SLAPI_LOG_PLUGIN))
        slapi_mods_dump(smods, "memberUid - mods dump - initial");
    for (smod = slapi_mods_get_first_smod(smods, nextMod); smod; smod
             = slapi_mods_get_next_smod(smods, nextMod)) {
        if (slapi_attr_types_equivalent(slapi_mod_get_type(smod), "uniqueMember")) {
            struct berval *bv;

            for (bv = slapi_mod_get_first_value(smod); bv;
                 bv = slapi_mod_get_next_value(smod)) {
                Slapi_Value *sv = slapi_value_new();

                slapi_value_init_berval(sv, bv); /* copies bv_val */
                if (SLAPI_IS_MOD_DELETE(slapi_mod_get_operation(smod))) {
                    del_mod = 1;
                    slapi_ch_array_add(&smod_deluids,
                                       slapi_ch_strdup(slapi_value_get_string(sv)));
                    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                    "modGroupMembership: add to deluids %s\n",
                                    bv->bv_val);
                } else {
                    slapi_ch_array_add(&smod_adduids,
                                       slapi_ch_strdup(slapi_value_get_string(sv)));
                    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                    "modGroupMembership: add to adduids %s\n",
                                    bv->bv_val);
                }
                slapi_value_free(&sv);
            }
        }
    }
    slapi_mod_free(&nextMod);

    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                    "modGroupMembership: entry is posixGroup\n");

    Slapi_Attr * muid_attr = NULL; /* Entry attributes        */
    Slapi_Value * uid_value = NULL; /* Attribute values        */

    char **adduids = NULL;
    char **moduids = NULL;
    char **deluids = NULL;
    int doModify = false;
    int j = 0;

    if (del_mod || smod_deluids != NULL) {
        do { /* Create a context to "break" from */
            Slapi_Attr * mu_attr = NULL; /* Entry attributes        */
            rc = slapi_entry_attr_find(entry, "memberUid", &mu_attr);
            if (rc != 0 || mu_attr == NULL) {
                slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                "modGroupMembership end: attribute memberUid not found\n");
                break;
            }
            /* found attribute uniquemember */
            if (smod_deluids == NULL) { /* deletion of the last value, deletes the Attribut from entry complete, this operation has no value, so we must look by self */
                Slapi_Attr * um_attr = NULL; /* Entry attributes        */
                Slapi_Value * uid_dn_value = NULL; /* Attribute values        */
                int rc = slapi_entry_attr_find(entry, "uniquemember", &um_attr);
                if (rc != 0 || um_attr == NULL) {
                    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                    "modGroupMembership end: attribute uniquemember not found\n");
                    break;
                }
                /* found attribute uniquemember */
                /* ...loop for value...    */
                for (j = slapi_attr_first_value(um_attr, &uid_dn_value); j != -1;
                     j = slapi_attr_next_value(um_attr, j, &uid_dn_value)) {
                    slapi_ch_array_add(&smod_deluids,
                                       slapi_ch_strdup(slapi_value_get_string(uid_dn_value)));
                }
            }
            /* ...loop for value...    */
            for (j = slapi_attr_first_value(mu_attr, &uid_value); j != -1;
                 j = slapi_attr_next_value(mu_attr, j, &uid_value)) {
                /* remove from uniquemember: remove from memberUid also */
                const char *uid = NULL;
                slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                "modGroupMembership: test dellist \n");
                uid = slapi_value_get_string(uid_value);
                slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                "modGroupMembership: test dellist %s\n", uid);
                if (uid_in_set(uid, smod_deluids)) {
                    slapi_ch_array_add(&deluids, slapi_ch_strdup(uid));
                    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                    "modGroupMembership: add to dellist %s\n", uid);
                    doModify = true;
                }
            }
        } while (false);
    }
    if (smod_adduids != NULL) { /* not MOD_DELETE */
        const char *uid_dn = NULL;

        slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                        "modGroupMembership: posixGroup -> look for uniquemember\n");
        /* found attribute uniquemember */
        for (j = 0; smod_adduids[j]; j++) {
            static char *uid = NULL;

            uid_dn = smod_adduids[j];
            slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                            "modGroupMembership: perform user %s\n", uid_dn);

            uid = searchUid(uid_dn);

            if (uid == NULL) {
                slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                "modGroupMembership: uid not found for %s, cannot do anything\n",
                                uid_dn); /* member on longer on server, do nothing */
            } else {
                rc |= slapi_entry_attr_find(entry, "memberUid", &muid_attr);
                if (rc != 0 || muid_attr == NULL) { /* Found no memberUid list, so create  */
                    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                    "modGroupMembership: no attribute memberUid, add with %s \n",
                                    uid_dn);
                    slapi_ch_array_add(&adduids, uid);
                    uid = NULL; /* adduids now owns uid */
                    doModify = true;
                } else { /* Found a memberUid list, so modify */
                    Slapi_ValueSet *vs = NULL;
                    Slapi_Value *v = slapi_value_new();

                    slapi_value_init_string_passin(v, uid);
                    slapi_attr_get_valueset(muid_attr, &vs);
                    if (slapi_valueset_find(muid_attr, vs, v) != NULL) { /* already exist, all ok */
                        slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                        "modGroupMembership: uid found in memberuid list %s nothing to do\n",
                                        uid);
                    } else {
                        slapi_ch_array_add(&moduids, uid);
                        slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                                        "modGroupMembership: add to modlist %s\n", uid);
                        uid = NULL; /* adduids now owns uid */
                        /* have to clear out v otherwise slapi_value_free will also free uid */
                        slapi_value_init_berval(v, NULL);
                        doModify = true;
                    }
                    slapi_value_free(&v); /* also frees uid since it was a passin */
                    slapi_valueset_free(vs); vs = NULL;
                }
            }
        }
    }
    if (doModify) {
        int i;
        for (i = 0; adduids && adduids[i]; i++) {
            if (!smods_has_mod(smods, LDAP_MOD_ADD, "memberUid", adduids[i])) {
                slapi_mods_add_string(smods, LDAP_MOD_ADD, "memberUid", adduids[i]);
            }
        }
        for (i = 0; moduids && moduids[i]; i++) {
            if (!smods_has_mod(smods, LDAP_MOD_ADD, "memberUid", moduids[i])) {
                slapi_mods_add_string(smods, LDAP_MOD_ADD, "memberUid", moduids[i]);
            }
        }
        for (i = 0; deluids && deluids[i]; i++) {
            if (!smods_has_mod(smods, LDAP_MOD_DELETE, "memberUid", deluids[i])) {
                slapi_mods_add_string(smods, LDAP_MOD_DELETE, "memberUid", deluids[i]);
            }
        }

        if (slapi_is_loglevel_set(SLAPI_LOG_PLUGIN))
            slapi_mods_dump(smods, "memberUid - mods dump");
        *do_modify = 1;
        posix_winsync_config_set_MOFTaskCreated();
    }
    slapi_ch_array_free(smod_adduids);
    smod_adduids = NULL;
    slapi_ch_array_free(adduids);
    adduids = NULL;
    slapi_ch_array_free(smod_deluids);
    smod_deluids = NULL;
    slapi_ch_array_free(deluids);
    deluids = NULL;
    slapi_ch_array_free(moduids);
    moduids = NULL;

    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME, "modGroupMembership: <==\n");
    return 0;
}

int
addGroupMembership(Slapi_Entry *entry, Slapi_Entry *ad_entry)
{
    int rc = 0;
    int i;

    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME, "addGroupMembership: ==>\n");

    if(!isPosixGroup(entry)) {
        return 0;
    }

    Slapi_Attr * um_attr = NULL; /* Entry attributes uniquemember        */
    Slapi_Attr * muid_attr = NULL; /* Entry attributes memebrof       */
    Slapi_Value * uid_value = NULL; /* uniquemember Attribute values        */

    Slapi_ValueSet *newvs = NULL;

    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                    "addGroupMembership: posixGroup -> look for uniquemember\n");
    rc = slapi_entry_attr_find(entry, "uniquemember", &um_attr);
    if (rc != 0 || um_attr == NULL) {
        slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                        "addGroupMembership end: attribute uniquemember not found\n");
        return 0;
    }
    /* found attribute uniquemember */
    rc = slapi_entry_attr_find(entry, "memberUid", &muid_attr);
    if (rc != 0 || muid_attr == NULL) { /* Found no memberUid list, so create  */
        slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                        "addGroupMembership: no attribute memberUid\n");
    }
    newvs = slapi_valueset_new();
    /* ...loop for value...    */
    for (i = slapi_attr_first_value(um_attr, &uid_value); i != -1;
         i = slapi_attr_next_value(um_attr, i, &uid_value)) {
        const char *uid_dn = NULL;
        static char *uid = NULL;
        Slapi_Value *v = NULL;

        uid_dn = slapi_value_get_string(uid_value);
        slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                        "addGroupMembership: perform member %s\n", uid_dn);
        uid = searchUid(uid_dn);
        if (uid == NULL) {
            slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME,
                            "addGroupMembership: uid not found for %s, cannot do anything\n",
                            uid_dn); /* member on longer on server, do nothing */
        } else {
            v = slapi_value_new_string(uid);
            slapi_ch_free_string(&uid);
            if (slapi_attr_value_find(muid_attr, slapi_value_get_berval(v)) != 0) {
                slapi_valueset_add_value(newvs, v);
            }
            slapi_value_free(&v);
        }
    }
    slapi_entry_add_valueset(entry, "memberUid", newvs);
    slapi_valueset_free(newvs);
    posix_winsync_config_get_MOFTaskCreated();

    slapi_log_error(SLAPI_LOG_PLUGIN, POSIX_WINSYNC_PLUGIN_NAME, "addGroupMembership: <==\n");
    return 0;
}

