root/usr/src/lib/libnisdb/ldap_nisdbquery.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2015 Gary Mills
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


#include <strings.h>
#include <string.h>
#include <lber.h>
#include <ldap.h>

#include "db_item_c.h"

#include "nisdb_mt.h"

#include "ldap_util.h"
#include "ldap_structs.h"
#include "ldap_val.h"
#include "ldap_ruleval.h"
#include "ldap_op.h"
#include "ldap_nisdbquery.h"
#include "ldap_attr.h"
#include "ldap_xdr.h"
#include "ldap_ldap.h"


item *
buildItem(int len, void *value) {
        char    *myself = "buildItem";
        item    *i = am(myself, sizeof (*i));
        int     mlen = len;

        if (i == 0)
                return (0);

        /*
         * To this function, a NULL value, or a length less than or equal
         * zero means an item with no value. Hence, buildItem(0, 0) is
         * _not_ the right way to create index_value == 0 to indicate
         * deletion.
         */
        if (value == 0 || len <= 0) {
                i->itemvalue.itemvalue_len = 0;
                i->itemvalue.itemvalue_val = 0;
                return (i);
        }

        /*
         * NIS+ usually stores the terminating NUL for strings, so we add
         * it here just in case. This means we usually waste a byte for
         * binary column values...
         */
        if (len > 0 && ((char *)value)[len-1] != '\0')
                mlen++;

        i->itemvalue.itemvalue_len = len;
        i->itemvalue.itemvalue_val = am(myself, mlen);
        if (mlen > 0 && i->itemvalue.itemvalue_val == 0) {
                free(i);
                return (0);
        }
        memcpy(i->itemvalue.itemvalue_val, value, len);

        return (i);
}

void
freeItem(item *i) {
        if (i != 0) {
                sfree(i->itemvalue.itemvalue_val);
                free(i);
        }
}

void
freeQcomp(db_qcomp *qc, int doFree) {

        if (qc == 0)
                return;

        freeItem(qc->index_value);
        if (doFree)
                free(qc);
}

db_query *
buildQuery(int num_components, db_qcomp *components) {
        char            *myself = "buildQuery";
        db_query        *q = am(myself, sizeof (*q));

        if (q == 0)
                return (0);

        q->components.components_len = num_components;
        q->components.components_val = components;

        return (q);
}

/*
 * Clone a db_query. The 'numComps' parameter can be used to specify
 * the number of db_qcomp's to allocate (in the 'components.components_val'
 * array), if 'components.components_len' hasn't yet reached its expected
 * maximum value.
 */
db_query *
cloneQuery(db_query *old, int numComps) {
        db_query        *new;
        int             i;
        char            *myself = "cloneQuery";

        if (old == 0)
                return (0);

        new = am(myself, sizeof (*new));
        if (new == 0)
                return (0);

        if (old->components.components_len > numComps)
                numComps = old->components.components_len;

        new->components.components_val = am(myself,
                                sizeof (new->components.components_val[0]) *
                                numComps);
        if (numComps > 0 && new->components.components_val == 0) {
                free(new);
                return (0);
        }

        for (i = 0; i < old->components.components_len; i++) {
                item    *it;

                if (old->components.components_val[i].index_value == 0) {
                        new->components.components_val[i].index_value = 0;
                        new->components.components_val[i].which_index =
                                old->components.components_val[i].which_index;
                        continue;
                }

                it = buildItem(old->components.components_val[i].index_value->
                                        itemvalue.itemvalue_len,
                                old->components.components_val[i].index_value->
                                        itemvalue.itemvalue_val);

                if (it == 0) {
                        new->components.components_len = i + 1;
                        freeQuery(new);
                        return (0);
                }

                new->components.components_val[i].index_value = it;
                new->components.components_val[i].which_index =
                        old->components.components_val[i].which_index;
        }

        new->components.components_len = old->components.components_len;

        return (new);
}

void
freeQuery(db_query *q) {
        int     i;

        if (q == 0)
                return;

        for (i = 0; i < q->components.components_len; i++) {
                freeItem(q->components.components_val[i].index_value);
        }

        sfree(q->components.components_val);
        sfree(q);
}

void
freeQueries(db_query **q, int numQ) {
        int     i;

        if (q == 0)
                return;

        for (i = 0; i < numQ; i++)
                freeQuery(q[i]);

        sfree(q);
}

/*
 * Given an array index[0..num-1] of pointers to strings of the form
 * "name=value", create the corresponding db_queries. "name=" indicates
 * deletion, which results in a db_query component where index_value == 0.
 *
 * The __nis_table_mapping_t structure is used to translate column
 * names to indices.
 *
 * If 'rvP' is non-NULL, the searchable columns from the 'index'
 * name/value pairs are used to retrieve copies of the corresponding NIS+
 * entries, and '*rvP' is initialized with the current entry values
 * and object attributes. Names/values supplied in 'index' override
 * those from existing NIS+ entries.
 */
db_query **
createQuery(int num, char **index, __nis_table_mapping_t *t,
                __nis_rule_value_t **rvP, int *numVals) {
        db_query                **q;
        db_qcomp                *qc;
        int                     i, j, n, a, nv, niv;
        __nis_rule_value_t      *rvq;
        __nis_buffer_t          b = {0, 0};
        char                    *table = 0;
        char                    *myself = "createQuery";

        rvq = initRuleValue(1, 0);
        if (rvq == 0)
                return (0);

        if (numVals == 0)
                numVals = &nv;
        *numVals = 0;

        if (rvP != 0) {
                /*
                 * Try to obtain a copy of the table object, in order to
                 * determine the searchable columns. A failure isn't
                 * necessarily fatal; we just try to compose the entire
                 * LDAP data from the col=val pairs.
                 */
                table = fullObjName(F, t->objName);
                if (table == 0) {
                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                "%s: Error converting \"%s\" to FQ object name",
                                myself, NIL(t->objName));
                        freeRuleValue(rvq, 1);
                        return (0);
                }
        }

        /* Create a rule-value from the col=val pairs */
        for (n = 0; n < num; n++) {
                char    *value;

                if ((value = strchr(index[n], '=')) == 0) {
                        logmsg(MSG_NOTIMECHECK, LOG_WARNING,
                                "%s: no '=' in \"%s\"",
                                myself, index[n]);
                        continue;
                }

                *value = '\0';
                value++;

                for (a = 0; a < t->numColumns; a++) {
                        if (strcmp(index[n], t->column[a]) == 0) {

                                /* Add col=val pair to 'rvq' */
                                if (addSCol2RuleValue(index[n], value, rvq)) {
                                        freeRuleValue(rvq, 1);
                                        sfree(table);
                                        return (0);
                                }

                                break;
                        }
                }
                if (a >= t->numColumns) {
                        logmsg(MSG_NOTIMECHECK, LOG_WARNING,
                                "%s: Ignoring unknown column \"%s\"",
                                myself, NIL(index[n]));
                }
        }

        /*
         * Find out if any of the columns specified via the 'index'
         * array are multi-valued.
         */
        for (n = 0, niv = 1; n < rvq->numColumns; n++) {
                if (rvq->colVal[n].numVals > 1)
                        niv *= rvq->colVal[n].numVals;
        }

        *numVals = 1;

        sfree(b.buf);
        sfree(table);

        if (rvq->numColumns <= 0) {
                freeRuleValue(rvq, *numVals);
                *numVals = 0;
                return (0);
        }

        /*
         * If any column name was repeated in the col=val pairs (but with
         * different values), 'rvq' will have one or more multi-valued
         * column values. We now convert those into an array of rule-values
         * where every column is single-valued.
         *
         * Since we want all combinations of column values, the number
         * of array elements is the product of all column value counts.
         *
         * There are four possible combinations of 'index' and NIS+ data:
         *
         * (1)  Only single-valued 'index' columns, and at most one NIS+
         *      entry, so 'rvq' is complete, and '*numVals' == 1.
         *
         * (2)  Single-valued 'index' columns, but multiple NIS+ entries.
         *      '*numVals' reflects the number of NIS+ entries, and no
         *      expansion of 'index' column values to array elements is
         *      needed.
         *
         * (3)  At least one multi-valued 'index', and multiple NIS+
         *      entries. We already rejected the NIS+ data for this case
         *      above, so it is in fact equivalent to case (4).
         *
         * (4)  At least one multi-valued 'index', but at most one NIS+
         *      entry. This is the case where we must expand the multi-valued
         *      columns to multiple array elements.
         */
        if (niv > 1 && *numVals == 1) {
                __nis_rule_value_t      *rv;
                int                     repeat;

                /*
                 * By using initRuleValue() to create 'rv', and make each
                 * element a clone of 'rvq', we save a lot of code. The
                 * down side is that 'rv' only really needs one element
                 * for each rv[].colVal[].val array, but we know that at
                 * least one rvq->colVal[].val array has more than one
                 * element. Hence, making 'rv' a clone of 'rvq' will waste
                 * memory.
                 *
                 * However, we believe this waste is acceptable, because
                 * we expect that 'niv' will be small. Also, we are executing
                 * in the context of a utility command, not in a daemon.
                 */
                rv = initRuleValue(niv, rvq);
                if (rv == 0) {
                        freeRuleValue(rvq, 1);
                        *numVals = 0;
                        return (0);
                }

                /*
                 * For each column value in 'rvq', copy to the appropriate
                 * place in 'rv', so that the end result is that all
                 * combinations of values are enumerated, and each
                 * 'rv[n].colVal[i]' is single-valued.
                 *
                 * We do this by traversing the rv[] array 'rvq->numColumns'
                 * times, where each traversal 'i' works on the values
                 * for rvq->colVal[i]. A repeat factor 'repeat' starts out
                 * at '1', and is multiplied by 'rvq->colVal[i].numVals'
                 * at the end of each traversal. Every value
                 * rvq->colVal[i].val[j] is repeated 'repeat' times.
                 *
                 * This algorithm works by regarding the rv[] array as
                 * an I-dimensional array (I = rvq->numColumns), where
                 * each dimension 'i' corresponds to the values for
                 * rvq->colVal[i]. The I-dimensional array is stored
                 * in column-major order.
                 *
                 * Since the 'rv' elements start out as copies of 'rvq',
                 * we achieve the "copy" of the 'rvq' column values by
                 * deleting those we don't want from the 'rv' elements.
                 */
                for (i = 0, repeat = 1; i < rvq->numColumns; i++) {
                        int     r, k;
                        for (n = 0, j = 0, r = 0; n < niv; n++) {
                                /*
                                 * Free all but element 'j' of the
                                 * rv[n].colVal[i].val array.
                                 */
                                for (k = 0; k < rv[n].colVal[i].numVals; k++) {
                                        /* Leave element 'j' in place */
                                        if (k == j)
                                                continue;
                                        sfree(rv[n].colVal[i].val[k].
                                                value);
                                }
                                rv[n].colVal[i].numVals = 1;
                                /* Move element 'j' to zero */
                                if (j != 0)
                                        rv[n].colVal[i].val[0] =
                                                rv[n].colVal[i].val[j];

                                /*
                                 * Increment the repeat index 'r'. If >=
                                 * 'repeat', reset 'r' and increment the
                                 * value index 'j'. If 'j' >=
                                 * rvq->colVal[i].numVals, start over on
                                 * the column values for column 'i' (i.e.,
                                 * reset 'j' to zero).
                                 */
                                r += 1;
                                if (r >= repeat) {
                                        r = 0;
                                        j += 1;
                                        if (j >= rvq->colVal[i].numVals)
                                                j = 0;
                                }
                        }
                        repeat *= rvq->colVal[i].numVals;
                }

                *numVals = niv;
                freeRuleValue(rvq, 1);
                rvq = rv;
                rv = 0;
        }

        q = am(myself, *numVals * sizeof (q[0]));
        if (q == 0) {
                freeRuleValue(rvq, *numVals);
                return (0);
        }

        /*
         * Create queries from the rvq[] array.
         */
        for (a = 0; a < *numVals; a++) {
                int     nn, err = 0;

                qc = am(myself, rvq[a].numColumns * sizeof (*qc));
                if (qc != 0) {
                        for (nn = 0, i = 0; i < rvq[a].numColumns; i++) {
                                for (j = 0; j < t->numColumns; j++) {
                                        if (strcmp(rvq[a].colName[i],
                                                        t->column[j]) == 0) {
                                                break;
                                        }
                                }
                                if (j >= t->numColumns)
                                        continue;
                                qc[nn].which_index = j;
                                if (rvq[a].colVal[i].numVals > 0) {
                                        qc[nn].index_value = buildItem(
                                                rvq[a].colVal[i].val[0].length,
                                                rvq[a].colVal[i].val[0].value);
                                        if (qc[nn].index_value == 0)
                                                err++;
                                } else {
                                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                                "%s: No values for [%d]%s",
                                                myself, a, rvq[a].colName[i]);
                                        err++;
                                }
                                nn++;
                        }
                        if (err == 0)
                                q[a] = buildQuery(nn, qc);
                }
                if (err > 0 || q[a] == 0) {
                        freeQueries(q, a);
                        for (a = 0; a < nn; a++)
                                freeQcomp(&qc[a], F);
                        sfree(qc);
                        freeRuleValue(rvq, *numVals);
                        return (0);
                }
        }

        if (rvP != 0) {
                *rvP = rvq;
        } else {
                freeRuleValue(rvq, 1);
                *numVals = 0;
        }

        return (q);
}

void
printQuery(db_query *q, __nis_table_mapping_t *t) {
        int     i, mc = -1;
        char    *myself = "printQuery";
        char    *val[NIS_MAXCOLUMNS];

        if (q == 0)
                return;

        (void) memset(val, 0, sizeof (val));

        /*
         * Collect the values, which may be out of order in 'q'.
         * Remember the largest index.
         */
        for (i = 0; i < q->components.components_len; i++) {
                int     ix = q->components.components_val[i].which_index;

                if (ix >= NIS_MAXCOLUMNS ||
                                (t != 0 && ix >= t->numColumns))
                        continue;
                if (ix > mc)
                        mc = ix;
                val[ix] = q->components.components_val[i].index_value->
                                itemvalue.itemvalue_val;
        }

        /* Print the values we collected */
        for (i = 0; i <= mc; i++) {
                p2buf(myself, "%s%s", (i != 0 ? " " : ""),
                        (val[i] != 0 ? val[i] : ""));
        }
        /* If we printed anything, add a newline */
        if (mc >= 0)
                p2buf(myself, "\n");
}

/*
 * Verify that the db_query's 'q' and 'fq' match, in the sense that if
 * they both have a value for a certain index, the values are the same.
 */
int
verifyQueryMatch(db_query *q, db_query *fq) {
        int     i, j, match;

        if (fq == 0)
                return (1);

        if (q == 0)
                return ((fq == 0) ? 1 : 0);

        for (i = 0, match = 1; match && i < q->components.components_len;
                        i++) {
                for (j = 0; j < fq->components.components_len; j++) {
                        int     len, flen;

                        /* Same index ? */
                        if (q->components.components_val[i].which_index !=
                                        fq->components.components_val[j].
                                                which_index)
                                continue;
                        /*
                         * If one 'index_value' is NULL, the other one must
                         * be NULL as well.
                         */
                        if (q->components.components_val[i].index_value == 0) {
                                if (fq->components.components_val[j].
                                                index_value == 0)
                                        continue;
                                else {
                                        match = 0;
                                        break;
                                }
                        }
                        if (fq->components.components_val[j].index_value ==
                                        0) {
                                match = 0;
                                break;
                        }
                        /* Same value lengths ? */
                        len = q->components.components_val[i].index_value->
                                itemvalue.itemvalue_len;
                        flen = fq->components.components_val[j].index_value->
                                itemvalue.itemvalue_len;
                        if (len != flen) {
                                /*
                                 * There's a twist here: the input query
                                 * may well _not_ count a concluding NUL
                                 * in a string value, while the output
                                 * usually will. So, if the difference in
                                 * length is one, and the "extra" byte is
                                 * a zero-valued one, we accept equality.
                                 * 'q' is assumed to be the output, and
                                 * 'fq' the input.
                                 */
                                if (!(len > 0 && len == (flen+1) &&
                                        q->components.components_val[i].
                                        index_value->
                                        itemvalue.itemvalue_val[len-1] == 0)) {
                                        match = 0;
                                        break;
                                }
                        }
                        /* Same value ? */
                        if (memcmp(q->components.components_val[i].index_value->
                                        itemvalue.itemvalue_val,
                                fq->components.components_val[j].index_value->
                                        itemvalue.itemvalue_val,
                                        flen) != 0) {
                                match = 0;
                                break;
                        }
                }
        }

        return (match);
}

/*
 * Remove those queries in 'q' that don't match t->index.
 * Returns a pointer to the filtered array, which could be
 * a compacted version of the original, or a new copy; in
 * the latter case, the original will have been freed.
 *
 * Filtered/removed db_query's are freed.
 */
db_query **
filterQuery(__nis_table_mapping_t *t, db_query **q, db_query *qin,
                __nis_obj_attr_t ***objAttr, int *numQueries) {
        db_query                **new;
        __nis_obj_attr_t        **attr;
        int                     i, nq, nn;
        char                    *myself = "filterQuery";

        if ((t == 0 && qin == 0) || q == 0 ||
                        numQueries == 0 || *numQueries <= 0)
                return (q);

        nq = *numQueries;
        new = am(myself, nq * sizeof (new[0]));
        if (objAttr != 0)
                attr = am(myself, nq * sizeof (attr[0]));
        else
                attr = 0;
        if (new == 0 || (objAttr != 0 && attr == 0)) {
                sfree(new);
                freeQueries(q, nq);
                sfree(attr);
                if (objAttr != 0) {
                        freeObjAttr(*objAttr, nq);
                        *objAttr = 0;
                }
                *numQueries = -1;
                return (0);
        }

        for (i = 0, nn = 0; i < nq; i++) {
                int     retain = 1;

                if (t != 0)
                        retain = verifyIndexMatch(t, q[i], 0, 0, 0);

                if (retain && qin != 0)
                        retain = verifyQueryMatch(q[i], qin);

                if (retain) {
                        new[nn] = q[i];
                        if (objAttr != 0)
                                attr[nn] = (*objAttr)[i];
                        nn++;
                } else {
                        freeQuery(q[i]);
                        q[i] = 0;
                        if (objAttr != 0) {
                                freeSingleObjAttr((*objAttr)[i]);
                                (*objAttr)[i] = 0;
                        }
                }
        }

        /* All q[i]'s are either in 'new', or have been deleted */
        free(q);
        if (objAttr != 0) {
                sfree(*objAttr);
                *objAttr = attr;
        }

        *numQueries = nn;

        return (new);
}

db_query **
createNisPlusEntry(__nis_table_mapping_t *t, __nis_rule_value_t *rv,
                        db_query *qin, __nis_obj_attr_t ***objAttr,
                        int *numQueries) {
        db_query                **query = 0;
        int                     r, i, j, ir;
        __nis_value_t           *rval, *lval;
        __nis_mapping_item_t    *litem;
        int                     numItems;
        int                     nq;
        __nis_obj_attr_t        **attr = 0;
        char                    **dn = 0;
        int                     numDN = 0;
        char                    *myself = "createNisPlusEntry";

        if (t == 0 || t->objectDN == 0 || rv == 0)
                return (0);

        /* Establish default, per-thread, search base */
        __nisdb_get_tsd()->searchBase = t->objectDN->read.base;

        for (r = 0, nq = 0; r < t->numRulesFromLDAP; r++) {
                int                     nrq, ntq, err;
                db_query                **newq;
                __nis_obj_attr_t        **newattr;

                rval = buildRvalue(&t->ruleFromLDAP[r]->rhs,
                        mit_ldap, rv, NULL);
                if (rval == 0)
                        continue;

                litem = buildLvalue(&t->ruleFromLDAP[r]->lhs, &rval,
                                        &numItems);
                if (litem == 0) {
                        freeValue(rval, 1);
                        /* XXX Should this be a fatal error ? */
                        continue;
                }

                lval = 0;
                for (i = 0; i < numItems; i++) {
                        __nis_value_t   *tmpval, *old;

                        tmpval = getMappingItem(&litem[i],
                                mit_nisplus, 0, 0, NULL);

                        /*
                         * If the LHS specifies an out-of-context LDAP or
                         * NIS+ item, we do the update right here. We
                         * don't add any values to 'lval'; instead, we
                         * skip to the next item. (However, we still
                         * get a string representation of the LHS in case
                         * we need to report an error.)
                         */
                        if (litem[i].type == mit_ldap) {
                                int     stat;

                                if (dn == 0)
                                        dn = findDNs(myself, rv, 1,
                                                t->objectDN->write.base,
                                                &numDN);

                                stat = storeLDAP(&litem[i], i, numItems, rval,
                                        t->objectDN, dn, numDN);
                                if (stat != LDAP_SUCCESS) {
                                        char    *iname = "<unknown>";

                                        if (tmpval != 0 &&
                                                        tmpval->numVals == 1)
                                                iname = tmpval->val[0].value;
                                        logmsg(MSG_NOTIMECHECK, LOG_ERR,
                                                "%s: LDAP store \"%s\": %s",
                                                myself, iname,
                                                ldap_err2string(stat));
                                }

                                freeValue(tmpval, 1);
                                continue;
                        }

                        old = lval;
                        lval = concatenateValues(old, tmpval);
                        freeValue(tmpval, 1);
                        freeValue(old, 1);
                }

                freeMappingItem(litem, numItems);
                if (lval == 0 || lval->numVals <= 0 || rval->numVals <= 0) {
                        freeValue(lval, 1);
                        freeValue(rval, 1);
                        continue;
                }

                /*
                 * We now have a number of possible cases. The notation
                 * used in the table is:
                 *
                 *      single          A single value (numVals == 1)
                 *      single/rep      A single value with repeat == 1
                 *      multi[N]        N values
                 *      multi[N]/rep    M values with repeat == 1
                 *      (M)             M resulting db_query's
                 *
                 * lval \ rval  single  single/rep      multi[N] multi[N]/rep
                 * single         (1)       (1)          (1)        (1)
                 * single/rep     (1)       (1)          (N)        (N)
                 * multi[M]       (1)       (1)          (1)     1+(N-1)/M
                 * multi[M]/rep   (1)       (1)          (1)     1+(N-1)/M
                 *
                 * Of course, we already have 'nq' db_query's from previous
                 * rules, so the resulting number of queries is max(1,nq)
                 * times the numbers in the table above.
                 */

                /* The number of queries resulting from the current rule */
                if (rval->numVals > 1) {
                        if (lval->numVals == 1 && lval->repeat)
                                nrq = rval->numVals;
                        else if (lval->numVals > 1 && rval->repeat)
                                nrq = 1 + ((rval->numVals-1)/lval->numVals);
                        else
                                nrq = 1;
                } else {
                        nrq = 1;
                }

                /* Total number of queries after adding the current rule */
                if (nq <= 0)
                        ntq = nrq;
                else
                        ntq = nq * nrq;

                if (ntq > nq) {
                        newq = realloc(query, ntq * sizeof (query[0]));
                        newattr = realloc(attr, ntq * sizeof (attr[0]));
                        if (newq == 0 || newattr == 0) {
                                logmsg(MSG_NOMEM, LOG_ERR,
                                        "%s: realloc(%d) => NULL",
                                        myself, ntq * sizeof (query[0]));
                                freeValue(lval, 1);
                                freeValue(rval, 1);
                                freeQueries(query, nq);
                                freeObjAttr(attr, nq);
                                sfree(newq);
                                freeDNs(dn, numDN);
                                return (0);
                        }
                        query = newq;
                        attr = newattr;
                }

                /*
                 * Copy/clone the existing queries to the new array,
                 * remembering that realloc() has done the first 'nq'
                 * ones.
                 *
                 * If there's an error (probably memory allocation), we
                 * still go through the rest of the array, so that it's
                 * simple to free the elements when we clean up.
                 */
                for (i = 1, err = 0; i < nrq; i++) {
                        for (j = 0; j < nq; j++) {
                                query[(nq*i)+j] = cloneQuery(query[j],
                                                t->numColumns);
                                if (query[(nq*i)+j] == 0 &&
                                                query[j] != 0)
                                        err++;
                                attr[(nq*i)+j] = cloneObjAttr(attr[j]);
                                if (attr[(nq*i)+j] == 0 &&
                                                attr[j] != 0)
                                        err++;
                        }
                }

                if (err > 0) {
                        freeValue(lval, 1);
                        freeValue(rval, 1);
                        freeQueries(query, ntq);
                        freeObjAttr(attr, ntq);
                        freeDNs(dn, numDN);
                        return (0);
                }

                /*
                 * Special case if nq == 0 (i.e., the first time we
                 * allocated db_query's). If so, we now allocate empty
                 * db_qcomp arrays, which simplifies subsequent
                 * copying of values.
                 */
                if (nq <= 0) {
                        (void) memset(query, 0, ntq * sizeof (query[0]));
                        (void) memset(attr, 0, ntq * sizeof (attr[0]));
                        for (i = 0, err = 0; i < ntq; i++) {
                                query[i] = am(myself, sizeof (*query[i]));
                                if (query[i] == 0) {
                                        err++;
                                        break;
                                }
                                query[i]->components.components_val =
                                        am(myself, t->numColumns *
                        sizeof (query[i]->components.components_val[0]));
                                if (query[i]->components.components_val == 0) {
                                        err++;
                                        break;
                                }
                                query[i]->components.components_len = 0;
                        }
                        if (err > 0) {
                                freeValue(lval, 1);
                                freeValue(rval, 1);
                                freeQueries(query, ntq);
                                freeObjAttr(attr, ntq);
                                freeDNs(dn, numDN);
                                return (0);
                        }
                }

                /* Now we're ready to add the new values */
                for (i = 0, ir = 0; i < lval->numVals; i++) {
                        char    *oaName = 0;
                        int     index;

                        /* Find column index */
                        for (index = 0; index < t->numColumns;
                                        index++) {
                                if (strncmp(t->column[index],
                                                lval->val[i].value,
                                        lval->val[i].length) == 0)
                                        break;
                        }
                        if (index >= t->numColumns) {
                                /*
                                 * Could be one of the special object
                                 * attributes.
                                 */
                                oaName = isObjAttr(&lval->val[i]);
                                if (oaName == 0)
                                        continue;
                        }

                        for (j = i*nrq; j < (i+1)*nrq; j++) {
                                int     k;

                                /* If we're out of values, repeat last one */
                                ir = (j < rval->numVals) ?
                                        j : rval->numVals - 1;

                                /*
                                 * Step through the query array, adding
                                 * the new value every 'nrq' queries, and
                                 * starting at 'query[j % nrq]'.
                                 */
                                for (k = j % nrq, err = 0; k < ntq; k += nrq) {
                                        int     ic, c;

                                        if (oaName != 0) {
                                                int     fail = setObjAttrField(
                                                                oaName,
                                                                &rval->val[ir],
                                                                &attr[k]);
                                                if (fail) {
                                                        err++;
                                                        break;
                                                }
                                                continue;
                                        }

                                        ic = query[k]->components.
                                                components_len;
                                        /*
                                         * If we've already filled this
                                         * query, the new value is a dup
                                         * which we'll ignore.
                                         */
                                        if (ic >= t->numColumns)
                                                continue;

                                        /*
                                         * Do we already have a value for
                                         * this 'index' ?
                                         */
                                        for (c = 0; c < ic; c++) {
                                                if (query[k]->components.
                                                        components_val[c].
                                                        which_index == index)
                                                        break;
                                        }

                                        /* If no previous value, add it */
                                        if (c >= ic) {
                                                int     l;
                                                char    *v;

                                                query[k]->components.
                                                        components_val[ic].
                                                        which_index = index;
                                                l = rval->val[ir].length;
                                                v = rval->val[ir].value;
                                                if (rval->type == vt_string &&
                                                        l > 0 &&
                                                        v[l-1] != '\0' &&
                                                        v[l] == '\0')
                                                        l++;
                                                query[k]->components.
                                                        components_val[ic].
                                                        index_value =
                                                        buildItem(l, v);
                                                if (query[k]->
                                                        components.
                                                        components_val[ic].
                                                        index_value == 0) {
                                                        err++;
                                                        break;
                                                }
                                                query[k]->components.
                                                        components_len++;
                                        }
                                }
                                if (err > 0) {
                                        freeValue(lval, 1);
                                        freeValue(rval, 1);
                                        freeQueries(query, ntq);
                                        freeObjAttr(attr, ntq);
                                        freeDNs(dn, numDN);
                                        return (0);
                                }
                        }
                }
                freeValue(lval, 1);
                freeValue(rval, 1);

                nq = ntq;
        }

        freeDNs(dn, numDN);

        if (nq <= 0) {
                sfree(query);
                query = 0;
        }

        /* Should we filter on index or input query ? */
        if (query != 0) {
                if (t->index.numIndexes > 0)
                        query = filterQuery(t, query, qin, &attr, &nq);
                else if (qin != 0)
                        query = filterQuery(0, query, qin, &attr, &nq);
        }

        if (query != 0 && numQueries != 0)
                *numQueries = nq;

        if (objAttr != 0)
                *objAttr = attr;
        else
                freeObjAttr(attr, nq);

        return (query);
}
/*
 * Given a table mapping and a rule-value, convert to an array of
 * (db_query *), using the fromLDAP ruleset.
 *
 * On entry, '*numQueries' holds the number of elements in the 'rv'
 * array. On exit, it holds the number of (db_query *)'s in the return
 * value array.
 */
db_query **
ruleValue2Query(__nis_table_mapping_t *t, __nis_rule_value_t *rv,
                db_query *qin, __nis_obj_attr_t ***objAttr, int *numQueries) {
        db_query                **q = 0, ***qp = 0;
        int                     i, nqp, nq, *nnp = 0, nv;
        __nis_obj_attr_t        **attr = 0, ***atp = 0;
        char                    *myself = "ruleValue2Query";


        if (t == 0 || rv == 0 || numQueries == 0)
                return (0);

        nv = *numQueries;
        if (nv <= 0)
                return (0);

        /*
         * 'qp' is an array of (db_query **), and we get one element for
         * each call to createNisPlusEntry(); i.e., one for each rule-value.
         *
         * 'nnp[i]' is the count of (db_query *) in each 'qp[i]'.
         */
        qp = am(myself, nv * sizeof (*qp));
        nnp = am(myself, nv * sizeof (*nnp));
        atp = am(myself, nv * sizeof (*atp));
        if (qp == 0 || nnp == 0 || atp == 0) {
                sfree(qp);
                sfree(nnp);
                sfree(atp);
                return (0);
        }

        for (i = 0, nq = 0, nqp = 0; i < nv; i++) {
                qp[nqp] = createNisPlusEntry(t, &rv[i], qin, &atp[nqp],
                                                &nnp[nqp]);
                /* If we fail, abort (XXX??? or continue ???) */
                if (qp[nqp] == 0)
                        goto cleanup;
                nq += nnp[nqp];
                nqp++;
        }

        /* If we didn't get any (db_query **)'s, return failure */
        if (nqp == 0 || nq <= 0)
                goto cleanup;

        q = am(myself, nq * sizeof (q[0]));
        attr = am(myself, nq * sizeof (attr[0]));
        if (q == 0 || attr == 0) {
                nq = 0;
                goto cleanup;
        }

        /* Convert 'qp' to an array of (db_query *)'s */
        for (i = 0, nq = 0; i < nqp; i++) {
                (void) memcpy(&q[nq], qp[i], nnp[i] * sizeof (qp[i][0]));
                (void) memcpy(&attr[nq], atp[i], nnp[i] * sizeof (atp[i][0]));
                nq += nnp[i];
                free(qp[i]);
                free(atp[i]);
        }

        *numQueries = nq;
        if (objAttr != 0)
                *objAttr = attr;
        else
                freeObjAttr(attr, nq);

        /* Make sure 'cleanup' doesn't free the db_query pointers */
        nqp = 0;

cleanup:
        for (i = 0; i < nqp; i++) {
                freeQueries(qp[i], nnp[i]);
                sfree(atp[i]);
        }
        sfree(qp);
        sfree(nnp);
        sfree(atp);

        return (q);
}

db_query *
pseudoEntryObj2Query(entry_obj *e, nis_object *tobj, __nis_rule_value_t *rv) {
        db_query                *qbuf;
        db_qcomp                *qcbuf;
        int                     nc, i;
        __nis_rule_value_t      *rvt = 0;
        char                    *myself = "pseudoEntryObj2Query";

        nc = e->en_cols.en_cols_len - 1;

        if (e == 0 || nc < 0 || nc > NIS_MAXCOLUMNS)
                return (0);

        /*
         * If 'rvP' is non-NULL, build a rule value from the pseudo-
         * nis_object in e->en_cols.en_cols_val[0].
         */
        if (rv != 0) {
                nis_object              *o;

                o = unmakePseudoEntryObj(e, tobj);
                if (o == 0)
                        return (0);
                rvt = addObjAttr2RuleValue(o, 0);
                nis_destroy_object(o);
                if (rvt == 0)
                        return (0);
        }

        qbuf = am(myself, sizeof (*qbuf));
        /*
         * If there are no columns (other than the pseudo-entry object),
         * we're done.
         */
        if (nc == 0)
                return (qbuf);

        qcbuf = am(myself, nc * sizeof (*qcbuf));
        if (qcbuf == 0) {
                sfree(qcbuf);
                if (rvt != 0)
                        freeRuleValue(rvt, 1);
                return (0);
        }

        /*
         * Build the db_query, remembering that e->en_cols.en_cols_val[0]
         * is the pseudo-nis_object.
         */
        qbuf->components.components_val = qcbuf;
        qbuf->components.components_len = nc;
        for (i = 0; i < nc; i++) {
                qcbuf[i].which_index = i;
                qcbuf[i].index_value = buildItem(
                        e->en_cols.en_cols_val[i+1].ec_value.ec_value_len,
                        e->en_cols.en_cols_val[i+1].ec_value.ec_value_val);
                if (qcbuf[i].index_value == 0) {
                        freeQuery(qbuf);
                        if (rvt != 0)
                                freeRuleValue(rvt, 1);
                        return (0);
                }
        }

        if (rvt != 0) {
                *rv = *rvt;
                sfree(rvt);
        }

        return (qbuf);
}

/*
 * Given an input query 'q', and a db_query work buffer 'qbuf', return
 * a pointer to a query with one component corresponding to component
 * 'index' in 'q'.
 *
 * Note that no memory is allocated, and that the returned query has
 * pointers into 'q'.
 */
db_query *
queryFromComponent(db_query *q, int index, db_query *qbuf) {

        if (q == 0 || index < 0 || index >= q->components.components_len ||
                        qbuf == 0)
                return (0);

        qbuf->components.components_len = 1;
        qbuf->components.components_val = &q->components.components_val[index];

        return (qbuf);
}