root/usr/src/lib/libnisdb/ldap_ruleval.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 <lber.h>
#include <ldap.h>
#include <strings.h>

#include "nisdb_mt.h"

#include "ldap_util.h"
#include "ldap_val.h"
#include "ldap_attr.h"
#include "ldap_ldap.h"
#include "ldap_ruleval.h"


/*
 * Free an array of 'count' rule-value elements.
 */
void
freeRuleValue(__nis_rule_value_t *rv, int count) {
        int     n, i, j;

        if (rv == 0)
                return;

        for (n = 0; n < count; n++) {

                if (rv[n].colName != 0) {
                        for (i = 0; i < rv[n].numColumns; i++) {
                                sfree(rv[n].colName[i]);
                        }
                        free(rv[n].colName);
                }
                if (rv[n].colVal != 0) {
                        for (i = 0; i < rv[n].numColumns; i++) {
                                for (j = 0; j < rv[n].colVal[i].numVals; j++) {
                                        sfree(rv[n].colVal[i].val[j].value);
                                }
                                if (rv[n].colVal[i].numVals > 0)
                                        sfree(rv[n].colVal[i].val);
                        }
                        free(rv[n].colVal);
                }

                if (rv[n].attrName != 0) {
                        for (i = 0; i < rv[n].numAttrs; i++) {
                                sfree(rv[n].attrName[i]);
                        }
                        free(rv[n].attrName);
                }
                if (rv[n].attrVal != 0) {
                        for (i = 0; i < rv[n].numAttrs; i++) {
                                for (j = 0; j < rv[n].attrVal[i].numVals;
                                                j++) {
                                        sfree(rv[n].attrVal[i].val[j].value);
                                }
                                if (rv[n].attrVal[i].numVals > 0)
                                        sfree(rv[n].attrVal[i].val);
                        }
                        free(rv[n].attrVal);
                }

        }
        sfree(rv);
}

/*
 * Return an array of 'count' __nis_rule_value_t elements, initialized
 * to be copies of 'rvIn' if supplied; empty otherwise.
 */
__nis_rule_value_t *
initRuleValue(int count, __nis_rule_value_t *rvIn) {
        return (growRuleValue(0, count, 0, rvIn));
}

static const __nis_rule_value_t rvZero = {0};

/*
 * Grow 'old' from 'oldCount' to 'newCount' elements, initialize the
 * new portion to 'rvIn' (empty if not supplied), and return a pointer
 * to the result. Following a call to this function, the caller must
 * refer only to the returned array, not to 'old'.
 */
__nis_rule_value_t *
growRuleValue(int oldCount, int newCount, __nis_rule_value_t *old,
                __nis_rule_value_t *rvIn) {
        __nis_rule_value_t      *rv;
        int                     i;
        char                    *myself = "growRuleValue";

        if (newCount <= 0 || newCount <= oldCount)
                return (old);

        if (oldCount <= 0) {
                oldCount = 0;
                old = 0;
        }

        if (rvIn == 0)
                rvIn = (__nis_rule_value_t *)&rvZero;

        rv = realloc(old, newCount * sizeof (rv[0]));
        if (rv == 0) {
                logmsg(MSG_NOMEM, LOG_ERR,
                        "%s: realloc(%d ((%d+%d)*%d)) => 0",
                        myself, (oldCount+newCount) * sizeof (rv[0]),
                        oldCount, newCount, sizeof (rv[0]));
                freeRuleValue(old, oldCount);
                return (0);
        }

        (void) memset(&rv[oldCount], 0, (newCount-oldCount)*sizeof (rv[0]));

        for (i = oldCount; i < newCount; i++) {
                rv[i].numColumns = rvIn->numColumns;
                if (rv[i].numColumns > 0) {
                        rv[i].colName = cloneName(rvIn->colName,
                                        rv[i].numColumns);
                        rv[i].colVal = cloneValue(rvIn->colVal,
                                        rv[i].numColumns);
                }
                if (rv[i].numColumns > 0 &&
                                (rv[i].colName == 0 || rv[i].colVal == 0)) {
                        freeRuleValue(rv, i);
                        return (0);
                }
                rv[i].numAttrs = rvIn->numAttrs;
                rv[i].attrName = cloneName(rvIn->attrName, rv[i].numAttrs);
                rv[i].attrVal = cloneValue(rvIn->attrVal, rv[i].numAttrs);
                if (rv[i].numAttrs > 0 &&
                        (rv[i].attrName == 0 || rv[i].attrVal == 0)) {
                        freeRuleValue(rv, i);
                        return (0);
                }
        }

        return (rv);
}

/*
 * Merge the source rule-value 's' into the target rule-value 't'.
 * If successful, unless 's' is a sub-set of 't', 't' will be changed
 * on exit, and will contain the values from 's' as well.
 */
int
mergeRuleValue(__nis_rule_value_t *t, __nis_rule_value_t *s) {
        int     i, j;

        if (s == 0)
                return (0);
        else if (t == 0)
                return (-1);

        for (i = 0; i < s->numColumns; i++) {
                for (j = 0; j < s->colVal[i].numVals; j++) {
                        if (addCol2RuleValue(s->colVal[i].type, s->colName[i],
                                        s->colVal[i].val[j].value,
                                        s->colVal[i].val[j].length,
                                        t))
                                return (-1);
                }
        }

        for (i = 0; i < s->numAttrs; i++) {
                for (j = 0; j < s->attrVal[i].numVals; j++) {
                        if (addAttr2RuleValue(s->attrVal[i].type,
                                        s->attrName[i],
                                        s->attrVal[i].val[j].value,
                                        s->attrVal[i].val[j].length,
                                        t))
                                return (-1);
                }
        }

        return (0);
}

static int
addVal2RuleValue(char *msg, int caseSens, int snipNul, __nis_value_type_t type,
                char *name, void *value, int valueLen,
                int *numP, char ***inNameP, __nis_value_t **inValP) {
        int                     i, j, copyLen = valueLen;
        __nis_single_value_t    *v;
        char                    **inName = *inNameP;
        __nis_value_t           *inVal = *inValP;
        int                     num = *numP;
        int                     (*comp)(const char *s1, const char *s2);
        char                    *myself = "addVal2RuleValue";

        /* Internal function, so assume arguments OK */

        if (msg == 0)
                msg = myself;

        /* Should we match the 'inName' value case sensitive or not ? */
        if (caseSens)
                comp = strcmp;
        else
                comp = strcasecmp;

        /*
         * String-valued NIS+ entries count the concluding NUL in the
         * length, while LDAP entries don't. In order to support this,
         * we implement the following for vt_string value types:
         *
         * If the last byte of the value isn't a NUL, add one to the
         * allocated length, so that there always is a NUL after the
         * value, making it safe to pass to strcmp() etc.
         *
         * If 'snipNul' is set (presumably meaning we're inserting a
         * value derived from a NIS+ entry), and the last byte of the
         * value already is a NUL, decrement the length to be copied by
         * one. This (a) doesn't count the NUL in the value length, but
         * (b) still leaves a NUL following the value.
         *
         * In N2L, for all cases we set 'copyLen' to the number of non-0
         * characters in 'value'.
         */
        if (type == vt_string && valueLen > 0) {
                char    *charval = value;

                if (charval[valueLen-1] != '\0')
                        valueLen += 1;
                else if (yp2ldap || snipNul)
                        copyLen -= 1;
        } else if (valueLen == 0) {
                /*
                 * If the 'value' pointer is non-NULL, we create a zero-
                 * length value with one byte allocated. This takes care
                 * of empty strings.
                 */
                valueLen += 1;
        }

        /* If we already have values for this attribute, add another one */
        for (i = 0; i < num; i++) {
                if ((*comp)(inName[i], name) == 0) {

                        /*
                         * Our caller often doesn't know the type of the
                         * value; this happens because the type (vt_string
                         * or vt_ber) is determined by the format in the
                         * rule sets, and we may be invoked as a preparation
                         * for evaluating the rules. Hence, we only use the
                         * supplied 'type' if we need to create a value.
                         * Otherwise, we accept mixed types.
                         *
                         * Strings are OK in any case, since we always make
                         * sure to have a zero byte at the end of any value,
                         * whatever the type.
                         */

                        if (inVal[i].numVals < 0) {
                                /*
                                 * Used to indicate deletion of attribute,
                                 * so we honor that and don't add a value.
                                 */
                                return (0);
                        }

                        /*
                         * If 'value' is NULL, we should delete, so
                         * remove any existing values, and set the
                         * 'numVals' field to -1.
                         */
                        if (value == 0) {
                                for (j = 0; j < inVal[i].numVals; j++) {
                                        sfree(inVal[i].val[j].value);
                                }
                                sfree(inVal[i].val);
                                inVal[i].val = 0;
                                inVal[i].numVals = -1;
                                return (0);
                        }

                        /* Is the value a duplicate ? */
                        for (j = 0; j < inVal[i].numVals; j++) {
                                if (copyLen == inVal[i].val[j].length &&
                                        memcmp(value, inVal[i].val[j].value,
                                                copyLen) == 0) {
                                        break;
                                }
                        }
                        if (j < inVal[i].numVals)
                                return (0);

                        /* Not a duplicate, so add the name/value pair */
                        v = realloc(inVal[i].val,
                                        (inVal[i].numVals+1) *
                                        sizeof (inVal[i].val[0]));
                        if (v == 0)
                                return (-1);
                        inVal[i].val = v;
                        v[inVal[i].numVals].length = copyLen;
                        v[inVal[i].numVals].value = am(msg, valueLen);
                        if (v[inVal[i].numVals].value == 0 &&
                                        value != 0) {
                                sfree(v);
                                return (-1);
                        }
                        memcpy(v[inVal[i].numVals].value, value, copyLen);
                        inVal[i].numVals++;

                        return (0);
                }
        }

        /* No previous value for this attribute */

        /*
         * value == 0 means deletion, in which case we create a
         * __nis_value_t with the numVals field set to -1.
         */
        if (value != 0) {
                if ((v = am(msg, sizeof (*v))) == 0)
                        return (-1);
                v->length = copyLen;
                v->value = am(msg, valueLen);
                if (v->value == 0 && value != 0) {
                        sfree(v);
                        return (-1);
                }
                memcpy(v->value, value, copyLen);
        }

        inVal = realloc(inVal, (num+1)*sizeof (inVal[0]));
        if (inVal == 0) {
                if (value != 0) {
                        sfree(v->value);
                        sfree(v);
                }
                return (-1);
        }
        *inValP = inVal;

        inName = realloc(inName,
                (num+1)*sizeof (inName[0]));
        if (inName == 0 || (inName[num] =
                        sdup(msg, T, name)) == 0) {
                sfree(v->value);
                sfree(v);
                return (-1);
        }
        *inNameP = inName;

        inVal[num].type = type;
        inVal[num].repeat = 0;
        if (value != 0) {
                inVal[num].numVals = 1;
                inVal[num].val = v;
        } else {
                inVal[num].numVals = -1;
                inVal[num].val = 0;
        }

        *numP += 1;

        return (0);
}

int
addAttr2RuleValue(__nis_value_type_t type, char *name, void *value,
                int valueLen, __nis_rule_value_t *rv) {
        char                    *myself = "addAttr2RuleValue";

        if (name == 0 || rv == 0)
                return (-1);

        return (addVal2RuleValue(myself, 0, 0, type, name, value, valueLen,
                                &rv->numAttrs, &rv->attrName, &rv->attrVal));
}

int
addSAttr2RuleValue(char *name, char *value, __nis_rule_value_t *rv) {
        return (addAttr2RuleValue(vt_string, name, value, slen(value), rv));
}

int
addCol2RuleValue(__nis_value_type_t type, char *name, void *value,
                int valueLen, __nis_rule_value_t *rv) {
        char *myself = "addCol2RuleValue";

        if (name == 0 || rv == 0)
                return (-1);

        return (addVal2RuleValue(myself, 1, 1, type, name, value, valueLen,
                                &rv->numColumns, &rv->colName, &rv->colVal));
}

int
addSCol2RuleValue(char *name, char *value, __nis_rule_value_t *rv) {
        return (addCol2RuleValue(vt_string, name, value, slen(value), rv));
}

/*
 * Given a table mapping, a NIS+ DB query, and (optionally) an existing
 * and compatible __nis_rule_value_t, return a new __nis_rule_value_t
 * with the values from the query added.
 */
__nis_rule_value_t *
buildNisPlusRuleValue(__nis_table_mapping_t *t, db_query *q,
                        __nis_rule_value_t *rv) {
        int                     i;

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

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

        for (i = 0; i < q->components.components_len; i++) {

                /* Ignore out-of-range column index */
                if (q->components.components_val[i].which_index >=
                                t->numColumns)
                        continue;

                /*
                 * Add the query value. A NULL value indicates deletion,
                 * but addCol2RuleValue() takes care of that for us.
                 */
                if (addCol2RuleValue(vt_string,
                                t->column[q->components.components_val[i].
                                                which_index],
                                q->components.components_val[i].index_value->
                                        itemvalue.itemvalue_val,
                                q->components.components_val[i].index_value->
                                        itemvalue.itemvalue_len, rv) != 0) {
                        freeRuleValue(rv, 1);
                        rv = 0;
                        break;
                }
        }

        return (rv);
}


/*
 * Given a LHS rule 'rl', return an array containing the item names,
 * and the number of elements in the array in '*numItems'.
 *
 * If there are 'me_match' __nis_mapping_element_t's, we use the
 * supplied '*rval' (if any) to derive values for the items in
 * the 'me_match', and add the values thus derived to '*rval' (in
 * which case the '*rval' pointer will change; the old '*rval'
 * is deleted).
 */
__nis_mapping_item_t *
buildLvalue(__nis_mapping_rlhs_t *rl, __nis_value_t **rval, int *numItems) {
        __nis_value_t           *val, *r;
        __nis_mapping_item_t    *item = 0;
        int                     i, n, ni = 0, nv = 0;
        int                     repeat = 0;

        if (rl == 0)
                return (0);

        if (rval != 0) {
                r = *rval;
                repeat = r->repeat;
        } else
                r = 0;

        /* If there is more than one element, we concatenate the items */
        for (i = 0; i < rl->numElements; i++) {
                __nis_mapping_element_t *e = &rl->element[i];
                __nis_mapping_item_t    *olditem, *tmpitem = 0;
                __nis_value_t           **tmp;

                switch (e->type) {
                case me_item:
                        tmpitem = cloneItem(&e->element.item);
                        break;
                case me_match:
                        /*
                         * Obtain values for the items in the 'me_match'
                         * element.
                         */
                        tmp = matchMappingItem(e->element.match.fmt, r, &nv,
                                0, 0);
                        if (tmp != 0) {
                                freeValue(r, 1);
                                val = 0;
                                for (n = 0; n < nv; n++) {
                                        r = concatenateValues(val, tmp[n]);
                                        freeValue(val, 1);
                                        freeValue(tmp[n], 1);
                                        val = r;
                                        if (val == 0) {
                                                for (n++; n < nv; n++) {
                                                        freeValue(tmp[n], 1);
                                                }
                                                break;
                                        }
                                }
                                free(tmp);
                                if (rval != 0) {
                                        if (repeat && val != 0)
                                                val->repeat = repeat;
                                        *rval = val;
                                }
                                for (n = 0; n < e->element.match.numItems;
                                                n++) {
                                        olditem = item;
                                        item = concatenateMappingItem(item, ni,
                                                &e->element.match.item[n]);
                                        freeMappingItem(olditem, ni);
                                        if (item == 0) {
                                                ni = 0;
                                                break;
                                        }
                                        ni++;
                                }
                        }
                        break;
                case me_print:
                case me_split:
                case me_extract:
                default:
                        /* These shouldn't show up on the LHS; ignore */
                        break;
                }

                if (tmpitem != 0) {
                        olditem = item;
                        item = concatenateMappingItem(item, ni, tmpitem);
                        freeMappingItem(olditem, ni);
                        freeMappingItem(tmpitem, 1);
                        ni++;
                        if (item == 0) {
                                ni = 0;
                                break;
                        }
                }
        }

        if (numItems != 0)
                *numItems = ni;

        return (item);
}

__nis_value_t *
buildRvalue(__nis_mapping_rlhs_t *rl, __nis_mapping_item_type_t native,
                __nis_rule_value_t *rv, int *stat) {
        __nis_value_t   *val, *vold = 0, *vnew;
        int             i;
        char            *myself = "buildRvalue";

        if (rl == 0 || rl->numElements <= 0) {
                /*
                 * No RHS indicates deletion, as does a __nis_value_t
                 * with numVals == -1, so we return such a creature.
                 */
                val = am(myself, sizeof (*val));
                if (val != 0) {
                        val->type = vt_string;
                        val->numVals = -1;
                }
                return (val);
        }

        /* If there is more than one element, we concatenate the values */
        for (i = 0; i < rl->numElements; i++) {
                vnew = getMappingElement(&rl->element[i], native, rv, stat);
                val = concatenateValues(vold, vnew);
                freeValue(vnew, 1);
                freeValue(vold, 1);
                vold = val;
        }
        return (val);
}

/*
 * Derive values for the LDAP attributes specified by the rule 'r',
 * and add them to the rule-value 'rv'.
 *
 * If 'doAssign' is set, out-of-context assignments are performed,
 * otherwise not.
 */
__nis_rule_value_t *
addLdapRuleValue(__nis_table_mapping_t *t,
                        __nis_mapping_rule_t *r,
                        __nis_mapping_item_type_t lnative,
                        __nis_mapping_item_type_t rnative,
                        __nis_rule_value_t *rv,
                        int doAssign, int *stat) {
        int                     i, j;
        __nis_value_t           *rval, *lval;
        __nis_mapping_item_t    *litem;
        int                     numItems;
        char                    **dn = 0;
        int                     numDN = 0;
        char                    *myself = "addLdapRuleValue";


        /* Do we have the required values ? */
        if (rv == 0)
                return (0);

        /*
         * Establish appropriate search base. For rnative == mit_nisplus,
         * we're deriving LDAP attribute values from NIS+ columns; in other
         * words, we're writing to LDAP, and should use the write.base value.
         */
        __nisdb_get_tsd()->searchBase = (rnative == mit_nisplus) ?
                t->objectDN->write.base : t->objectDN->read.base;

        /* Set escapeFlag if LHS is "dn" to escape special chars */
        if (yp2ldap && r->lhs.numElements == 1 &&
                r->lhs.element->type == me_item &&
                r->lhs.element->element.item.type == mit_ldap &&
                strcasecmp(r->lhs.element->element.item.name, "dn") == 0) {
                        __nisdb_get_tsd()->escapeFlag = '1';
        }

        /* Build the RHS value */
        rval = buildRvalue(&r->rhs, rnative, rv, stat);

        /* Reset escapeFlag */
        __nisdb_get_tsd()->escapeFlag = '\0';

        if (rval == 0)
                return (rv);

        /*
         * Special case: If we got no value for the RHS (presumably because
         * we're missing one or more item values), we don't produce an lval.
         * Note that this isn't the same thing as an empty value, which we
         * faithfully try to transmit to LDAP.
         */
        if (rval->numVals == 1 && rval->val[0].value == 0) {
                freeValue(rval, 1);
                return (rv);
        }

        /* Obtain the LHS item names */
        litem = buildLvalue(&r->lhs, &rval, &numItems);
        if (litem == 0) {
                freeValue(rval, 1);
                return (rv);
        }

        /* Get string representations of the LHS item names */
        lval = 0;
        for (i = 0; i < numItems; i++) {
                __nis_value_t   *tmpval, *old;

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

                /*
                 * If the LHS item is out-of-context, we do the
                 * assignment right here.
                 */
                if (doAssign && litem[i].type == mit_ldap &&
                                litem[i].searchSpec.triple.scope !=
                                        LDAP_SCOPE_UNKNOWN &&
                                slen(litem[i].searchSpec.triple.base) > 0 &&
                                (slen(litem[i].searchSpec.triple.attrs) > 0 ||
                                litem[i].searchSpec.triple.element != 0)) {
                        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);
        }

        /* Don't need the LHS items themselves anymore */
        freeMappingItem(litem, numItems);

        /*
         * If we don't have an 'lval' (probably because all litem[i]:s
         * were out-of-context assignments), we're done.
         */
        if (lval == 0 || lval->numVals <= 0) {
                freeValue(lval, 1);
                freeValue(rval, 1);
                return (rv);
        }

        for (i = 0, j = 0; i < lval->numVals; i++) {
                /* Special case: rval->numVals < 0 means deletion */
                if (rval->numVals < 0) {
                        (void) addAttr2RuleValue(rval->type,
                                lval->val[i].value, 0, 0, rv);
                        continue;
                }
                /* If we're out of values, repeat the last one */
                if (j >= rval->numVals)
                        j = (rval->numVals > 0) ? rval->numVals-1 : 0;
                for (; j < rval->numVals; j++) {
                        /*
                         * If this is the 'dn', and the value ends in a
                         * comma, append the appropriate search base.
                         */
                        if (strcasecmp("dn", lval->val[i].value) == 0 &&
                                        lastChar(&rval->val[j]) == ',' &&
                                        t->objectDN->write.scope !=
                                                LDAP_SCOPE_UNKNOWN) {
                                void    *nval;
                                int     nlen = -1;

                                nval = appendString2SingleVal(
                                        t->objectDN->write.base, &rval->val[j],
                                        &nlen);
                                if (nval != 0 && nlen >= 0) {
                                        sfree(rval->val[j].value);
                                        rval->val[j].value = nval;
                                        rval->val[j].length = nlen;
                                }
                        }
                        (void) addAttr2RuleValue(rval->type,
                                lval->val[i].value, rval->val[j].value,
                                rval->val[j].length, rv);
                        /*
                         * If the lval is multi-valued, go on to the
                         * other values; otherwise, quit (but increment
                         * the 'rval' value index).
                         */
                        if (!lval->repeat) {
                                j++;
                                break;
                        }
                }
        }

        /* Clean up */
        freeValue(lval, 1);
        freeValue(rval, 1);

        return (rv);
}

/*
 * Remove the indicated attribute, and any values for it, from the
 * rule-value.
 */
void
delAttrFromRuleValue(__nis_rule_value_t *rv, char *attrName) {
        int     i;

        if (rv == 0 || attrName == 0)
                return;

        for (i = 0; i < rv->numAttrs; i++) {
                if (strcasecmp(attrName, rv->attrName[i]) == 0) {
                        int     j;

                        for (j = 0; j < rv->attrVal[i].numVals; j++)
                                sfree(rv->attrVal[i].val[j].value);
                        if (rv->attrVal[i].numVals > 0)
                                sfree(rv->attrVal[i].val);

                        sfree(rv->attrName[i]);

                        /* Move up the rest of the attribute names/values */
                        for (j = i+1; j < rv->numAttrs; j++) {
                                rv->attrName[j-1] = rv->attrName[j];
                                rv->attrVal[j-1] = rv->attrVal[j];
                        }

                        rv->numAttrs -= 1;

                        break;
                }
        }
}

/*
 * Remove the indicated column, and any values for it, from the
 * rule-value.
 */
void
delColFromRuleValue(__nis_rule_value_t *rv, char *colName) {
        int     i;

        if (rv == 0 || colName == 0)
                return;

        for (i = 0; i < rv->numColumns; i++) {
                if (strcmp(colName, rv->colName[i]) == 0) {
                        int     j;

                        for (j = 0; j < rv->colVal[i].numVals; j++)
                                sfree(rv->colVal[i].val[j].value);
                        if (rv->colVal[i].numVals > 0)
                                sfree(rv->colVal[i].val);

                        sfree(rv->colName[i]);

                        /* Move up the rest of the column names/values */
                        for (j = i+1; j < rv->numColumns; j++) {
                                rv->colName[j-1] = rv->colName[j];
                                rv->colVal[j-1] = rv->colVal[j];
                        }

                        rv->numColumns -= 1;

                        break;
                }
        }
}

/*
 * Add the write-mode object classes specified by 'objClassAttrs' to the
 * rule-value 'rv'.
 * If there's an error, 'rv' is deleted, and NULL returned.
 */
__nis_rule_value_t *
addObjectClasses(__nis_rule_value_t *rv, char *objClassAttrs) {
        char    *filter = 0, **fc = 0;
        int     i, nfc = 0;

        /*
         * Expect to only use this for existing rule-values, so rv == 0 is
         * an error.
         */
        if (rv == 0)
                return (0);

        /*
         * If 'objClassAttrs' is NULL, we trivially have nothing to do.
         * Assume the caller knows what it's doing, and return success.
         */
        if (objClassAttrs == 0)
                return (rv);

        /*
         * Make an AND-filter of the object classes, and split into
         * components. (Yes, this is a bit round-about, but leverages
         * existing functions.)
         */
        filter = makeFilter(objClassAttrs);
        if (filter == 0) {
                freeRuleValue(rv, 1);
                return (0);
        }

        fc = makeFilterComp(filter, &nfc);
        if (fc == 0 || nfc <= 0) {
                free(filter);
                freeRuleValue(rv, 1);
                return (0);
        }

        /* Add the objectClass attributes to the rule-value */
        for (i = 0; i < nfc; i++) {
                char    *name, *value;

                name = fc[i];
                /* Skip if not of the "name=value" form */
                if ((value = strchr(name, '=')) == 0)
                        continue;

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

                /* Skip if the attribute name isn't "objectClass" */
                if (strcasecmp("objectClass", name) != 0)
                        continue;

                if (addSAttr2RuleValue(name, value, rv) != 0) {
                        free(filter);
                        freeFilterComp(fc, nfc);
                        freeRuleValue(rv, 1);
                        return (0);
                }
        }

        free(filter);
        freeFilterComp(fc, nfc);

        return (rv);
}


static char *
valString(__nis_value_t *val) {
        int     i;

        if (val == 0 || val->type != vt_string)
                return (0);

        for (i = 0; i < val->numVals; i++) {
                /* Look for a non-NULL, non-zero length value */
                if (val->val[i].value != 0 && val->val[i].length > 0) {
                        char    *v = val->val[i].value;

                        /*
                         * Check that there's a NUL at the end. True,
                         * if there isn't, we may be looking beyond
                         * allocated memory. However, we would have done
                         * so in any case when the supposed string was
                         * traversed (printed, etc.), very possibly by
                         * a lot more than one byte. So, it's better to
                         * take a small risk here than a large one later.
                         */
                        if (v[val->val[i].length-1] == '\0' ||
                                        v[val->val[i].length] == '\0')
                                return (v);
                }
        }

        return (0);
}

char *
findVal(char *name, __nis_rule_value_t *rv, __nis_mapping_item_type_t type) {
        int     i;

        if (type == mit_nisplus) {
                for (i = 0; i < rv->numColumns; i++) {
                        if (rv->colName[i] == 0)
                                continue;
                        if (strcmp(name, rv->colName[i]) == 0) {
                                return (valString(&rv->colVal[i]));
                        }
                }
        } else if (type == mit_ldap) {
                for (i = 0; i < rv->numAttrs; i++) {
                        if (rv->attrName[i] == 0)
                                continue;
                        if (strcasecmp(name, rv->attrName[i]) == 0) {
                                return (valString(&rv->attrVal[i]));
                        }
                }
        }

        return (0);
}

static char     *norv = "<NIL>";
static char     *unknown = "<unknown>";

/*
 * Attempt to derive a string identifying the rule-value 'rv'. The
 * returned string is a pointer, either into 'rv', or to static
 * storage, and must not be freed.
 */
char *
rvId(__nis_rule_value_t *rv, __nis_mapping_item_type_t type) {
        char    *v;

        if (rv == 0)
                return (norv);

        if (rv->numColumns > 0 && type == mit_nisplus) {
                /*
                 * Look for a column called "cname" or "name".
                 * If that fails, try "key" or "alias".
                 */
                if ((v = findVal("cname", rv, type)) != 0)
                        return (v);
                else if ((v = findVal("name", rv, type)) != 0)
                        return (v);
                else if ((v = findVal("key", rv, type)) != 0)
                        return (v);
                else if ((v = findVal("alias", rv, type)) != 0)
                        return (v);
        } else if (rv->numAttrs > 0 && type == mit_ldap) {
                /*
                 * Look for "dn", or "cn".
                 */
                if ((v = findVal("dn", rv, type)) != 0)
                        return (v);
                else if ((v = findVal("cn", rv, type)) != 0)
                        return (v);
        }

        return (unknown);
}

/*
 * Merge the rule-values with the same DN into one. Each rule-value
 * in the returned array will have unique 'dn'. On entry, *numVals
 * contains the number of rule-values in 'rv'. On exit, it contains
 * the number of rule-values in the returned array or -1 on error.
 */
__nis_rule_value_t *
mergeRuleValueWithSameDN(__nis_rule_value_t *rv, int *numVals) {
        __nis_rule_value_t      *rvq = 0;
        char                    *dn, *odn;
        int                     count = 0;
        int                     i, j;

        if (numVals == 0)
                return (0);

        for (i = 0; i < *numVals; i++) {
                if ((dn = findVal("dn", &rv[i], mit_ldap)) != 0) {
                        for (j = 0; j < count; j++) {
                                if ((odn = findVal("dn", &rvq[j],
                                                mit_ldap)) != 0) {
                                        /* case sensitive compare */
                                        if (strcmp(dn, odn) != 0)
                                                continue;
                                        if (mergeRuleValue(&rvq[j],
                                                        &rv[i]) == -1) {
                                                freeRuleValue(rvq, count);
                                                *numVals = -1;
                                                return (0);
                                        }
                                        break;
                                } else {
                                        freeRuleValue(rvq, count);
                                        *numVals = -1;
                                        return (0);
                                }
                        }
                        /* if no match, then add it to the rulevalue array */
                        if (j == count) {
                                rvq = growRuleValue(count, count + 1, rvq,
                                                                        &rv[i]);
                                if (rvq == 0) {
                                        *numVals = -1;
                                        return (0);
                                }
                                count++;
                        }
                }
        }

        *numVals = count;
        return (rvq);
}