/* addpartial-overlay.c */
/* $OpenLDAP: pkg/ldap/contrib/slapd-modules/addpartial/addpartial-overlay.c,v 1.1.2.8 2011/01/26 23:23:28 quanah Exp $ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 2004-2011 The OpenLDAP Foundation.
 * Portions Copyright (C) 2004 Virginia Tech, David Hawes.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * http://www.OpenLDAP.org/license.html.
 */
/* ACKNOLEDGEDMENTS:
 * This work was initially developed by David Hawes of Virginia Tech
 * for inclusion in OpenLDAP Software.
 */
/* addpartial-overlay
 *
 * This is an OpenLDAP overlay that intercepts ADD requests, determines if a
 * change has actually taken place for that record, and then performs a modify
 * request for those values that have changed (modified, added, deleted).  If
 * the record has not changed in any way, it is ignored.  If the record does not
 * exist, the record falls through to the normal add mechanism.  This overlay is
 * useful for replicating from sources that are not LDAPs where it is easier to
 * build entire records than to determine the changes (i.e. a database). 
 */

#include "portable.h" 
#include "slap.h"

static int collect_error_msg_cb( Operation *op, SlapReply *rs);

static slap_overinst addpartial;

/**
 *  The meat of the overlay.  Search for the record, determine changes, take
 *  action or fall through.
 */
static int addpartial_add( Operation *op, SlapReply *rs)
{
    Operation nop = *op;
    Entry *toAdd = NULL;
    Entry *found = NULL;
    slap_overinst *on = (slap_overinst *) op->o_bd->bd_info;
    int rc;

    toAdd = op->oq_add.rs_e;

    Debug(LDAP_DEBUG_TRACE, "%s: toAdd->e_nname.bv_val: %s\n",
          addpartial.on_bi.bi_type, toAdd->e_nname.bv_val,0);

    /* if the user doesn't have access, fall through to the normal ADD */
    if(!access_allowed(op, toAdd, slap_schema.si_ad_entry,
                       NULL, ACL_WRITE, NULL))
    {
        return SLAP_CB_CONTINUE;
    }

    rc = overlay_entry_get_ov(&nop, &nop.o_req_ndn, NULL, NULL, 0, &found, on);

    if(rc != LDAP_SUCCESS)
    {
        Debug(LDAP_DEBUG_TRACE,
              "%s: no entry found, falling through to normal add\n",
              addpartial.on_bi.bi_type, 0, 0);
        return SLAP_CB_CONTINUE;
    }
    else
    { 
        Debug(LDAP_DEBUG_TRACE, "%s: found the dn\n", addpartial.on_bi.bi_type,
              0,0);

        if(found)
        {
            Attribute *attr = NULL;
            Attribute *at = NULL;
            int ret;
            Modifications *mods = NULL;
            Modifications **modtail = &mods;
            Modifications *mod = NULL;

            Debug(LDAP_DEBUG_TRACE, "%s: have an entry!\n",
                  addpartial.on_bi.bi_type,0,0);

           /* determine if the changes are in the found entry */ 
            for(attr = toAdd->e_attrs; attr; attr = attr->a_next)
            {
                if(attr->a_desc->ad_type->sat_atype.at_usage != 0) continue;

                at = attr_find(found->e_attrs, attr->a_desc);
                if(!at)
                {
                    Debug(LDAP_DEBUG_TRACE, "%s: Attribute %s not found!\n",
                          addpartial.on_bi.bi_type,
                          attr->a_desc->ad_cname.bv_val,0);
                    mod = (Modifications *) ch_malloc(sizeof(
                                                            Modifications));
                    mod->sml_flags = 0;
                    mod->sml_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
                    mod->sml_op &= LDAP_MOD_OP;
                    mod->sml_next = NULL;
                    mod->sml_desc = attr->a_desc;
                    mod->sml_type = attr->a_desc->ad_cname;
                    mod->sml_values = attr->a_vals;
                    mod->sml_nvalues = attr->a_nvals;
                    mod->sml_numvals = attr->a_numvals;
                    *modtail = mod;
                    modtail = &mod->sml_next;
                }
                else
                {
                    MatchingRule *mr = attr->a_desc->ad_type->sat_equality;
                    struct berval *bv;
                    const char *text;
                    int acount , bcount;
                    Debug(LDAP_DEBUG_TRACE, "%s: Attribute %s found\n",
                          addpartial.on_bi.bi_type,
                          attr->a_desc->ad_cname.bv_val,0);

                    for(bv = attr->a_vals, acount = 0; bv->bv_val != NULL; 
                        bv++, acount++)
                    {
                        /* count num values for attr */
                    }
                    for(bv = at->a_vals, bcount = 0; bv->bv_val != NULL; 
                        bv++, bcount++)
                    {
                        /* count num values for attr */
                    }
                    if(acount != bcount)
                    {
                        Debug(LDAP_DEBUG_TRACE, "%s: acount != bcount, %s\n",
                              addpartial.on_bi.bi_type,
                              "replace all",0);
                        mod = (Modifications *) ch_malloc(sizeof(
                                                                Modifications));
                        mod->sml_flags = 0;
                        mod->sml_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
                        mod->sml_op &= LDAP_MOD_OP;
                        mod->sml_next = NULL;
                        mod->sml_desc = attr->a_desc;
                        mod->sml_type = attr->a_desc->ad_cname;
                        mod->sml_values = attr->a_vals;
                        mod->sml_nvalues = attr->a_nvals;
                        mod->sml_numvals = attr->a_numvals;
                        *modtail = mod;
                        modtail = &mod->sml_next;
                        continue;
                    }
                    
                    for(bv = attr->a_vals; bv->bv_val != NULL; bv++)
                    {
                        struct berval *v;
                        ret = -1;
                        
                        for(v = at->a_vals; v->bv_val != NULL; v++)
                        {
                            int r;
                            if(mr && ((r = value_match(&ret, attr->a_desc, mr,
                                           SLAP_MR_VALUE_OF_ASSERTION_SYNTAX,
                                           bv, v, &text)) == 0))
                            {
                                if(ret == 0)
                                    break;
                            }
                            else
                            {
                                Debug(LDAP_DEBUG_TRACE,
                                      "%s: \tvalue DNE, r: %d \n",
                                      addpartial.on_bi.bi_type,
                                      r,0);
                                ret = strcmp(bv->bv_val, v->bv_val);
                                if(ret == 0)
                                    break;
                            }
                        }

                        if(ret == 0)
                        {
                            Debug(LDAP_DEBUG_TRACE,
                                  "%s: \tvalue %s exists, ret: %d\n",
                                  addpartial.on_bi.bi_type, bv->bv_val, ret);
                        }
                        else
                        {
                            Debug(LDAP_DEBUG_TRACE,
                                  "%s: \tvalue %s DNE, ret: %d\n",
                                  addpartial.on_bi.bi_type, bv->bv_val, ret);
                            mod = (Modifications *) ch_malloc(sizeof(
                                                                Modifications));
                            mod->sml_flags = 0;
                            mod->sml_op = LDAP_MOD_REPLACE | LDAP_MOD_BVALUES;
                            mod->sml_op &= LDAP_MOD_OP;
                            mod->sml_next = NULL;
                            mod->sml_desc = attr->a_desc;
                            mod->sml_type = attr->a_desc->ad_cname;
                            mod->sml_values = attr->a_vals;
                            mod->sml_nvalues = attr->a_nvals;
                            mod->sml_numvals = attr->a_numvals;
                            *modtail = mod;
                            modtail = &mod->sml_next;
                            break;
                        }
                    }
                }
            }

            /* determine if any attributes were deleted */
            for(attr = found->e_attrs; attr; attr = attr->a_next)
            {
                if(attr->a_desc->ad_type->sat_atype.at_usage != 0) continue;

                at = NULL;
                at = attr_find(toAdd->e_attrs, attr->a_desc);
                if(!at)
                {
                    Debug(LDAP_DEBUG_TRACE,
                          "%s: Attribute %s not found in new entry!!!\n",
                          addpartial.on_bi.bi_type,
                          attr->a_desc->ad_cname.bv_val, 0);
                    mod = (Modifications *) ch_malloc(sizeof(
                                                        Modifications));
                    mod->sml_flags = 0;
                    mod->sml_op = LDAP_MOD_REPLACE;
                    mod->sml_next = NULL;
                    mod->sml_desc = attr->a_desc;
                    mod->sml_type = attr->a_desc->ad_cname;
                    mod->sml_values = NULL;
                    mod->sml_nvalues = NULL;
                    mod->sml_numvals = 0;
                    *modtail = mod;
                    modtail = &mod->sml_next;
                }
                else
                {
                    Debug(LDAP_DEBUG_TRACE,
                          "%s: Attribute %s found in new entry\n",
                          addpartial.on_bi.bi_type,
                          at->a_desc->ad_cname.bv_val, 0);
                }
            }

            overlay_entry_release_ov(&nop, found, 0, on);

            if(mods)
            {
                Modifications *m = NULL;
                Modifications *toDel;
                int modcount;
                slap_callback nullcb = { NULL, collect_error_msg_cb, 
                                         NULL, NULL };

                Debug(LDAP_DEBUG_TRACE, "%s: mods to do...\n",
                      addpartial.on_bi.bi_type, 0, 0);

                nop.o_tag = LDAP_REQ_MODIFY;
                nop.orm_modlist = mods;
                nop.orm_no_opattrs = 0;
                nop.o_callback = &nullcb;
                nop.o_bd->bd_info = (BackendInfo *) on->on_info;

                for(m = mods, modcount = 0; m; m = m->sml_next, 
                    modcount++)
                {
                    /* count number of mods */
                }

                Debug(LDAP_DEBUG_TRACE, "%s: number of mods: %d\n",
                      addpartial.on_bi.bi_type, modcount, 0);

                if(nop.o_bd->be_modify)
                {
                    SlapReply nrs = { REP_RESULT };
                    rc = (nop.o_bd->be_modify)(&nop, &nrs);
                }

                if(rc == LDAP_SUCCESS)
                {
                    Debug(LDAP_DEBUG_TRACE,
                          "%s: modify successful\n",
                          addpartial.on_bi.bi_type, 0, 0);
                }
                else
                {
                    Debug(LDAP_DEBUG_TRACE, "%s: modify unsuccessful: %d\n",
                          addpartial.on_bi.bi_type, rc, 0);
                    rs->sr_err = rc;
                    if(nullcb.sc_private)
                    {
                        rs->sr_text = nullcb.sc_private;
                    }
                }

                Debug(LDAP_DEBUG_TRACE, "%s: freeing mods...\n",
                      addpartial.on_bi.bi_type, 0, 0);

                for(toDel = mods; toDel; toDel = mods)
                {
                    mods = mods->sml_next;
                    ch_free(toDel);
                }
            }
            else
            {
                Debug(LDAP_DEBUG_TRACE, "%s: no mods to process\n",
                      addpartial.on_bi.bi_type, 0, 0);
            }
        }
        else
        {
            Debug(LDAP_DEBUG_TRACE, "%s: no entry!\n",
                  addpartial.on_bi.bi_type, 0, 0);
        }

        op->o_callback = NULL;
        send_ldap_result( op, rs );
        ch_free((void *)rs->sr_text);
        rs->sr_text = NULL;

        return LDAP_SUCCESS;
    }
}

static int collect_error_msg_cb( Operation *op, SlapReply *rs)
{
    if(rs->sr_text)
    {
        op->o_callback->sc_private = (void *) ch_strdup(rs->sr_text);
    }

    return LDAP_SUCCESS;
}

int addpartial_init() 
{
    addpartial.on_bi.bi_type = "addpartial";
    addpartial.on_bi.bi_op_add = addpartial_add;

    return (overlay_register(&addpartial));
}

int init_module(int argc, char *argv[]) 
{
    return addpartial_init();
}
