/* * 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 (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * DESCRIPTION: Contains dit_access interface support functions. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../ldap_util.h" #include "../ldap_map.h" #include "../ldap_parse.h" #include "../ldap_structs.h" #include "../ldap_val.h" #include "../ldap_ruleval.h" #include "../ldap_op.h" #include "../ldap_attr.h" #include "../ldap_nisdbquery.h" #include "../nisdb_mt.h" #include "shim.h" #include "yptol.h" #include "dit_access_utils.h" #define YPMULTI "YP_MULTI_" #define YPMULTISZ 9 /* == strlen(YPMULTI) */ /* * Returns 'map,domain.' */ char * getFullMapName(char *map, char *domain) { char *myself = "getFullMapName"; char *objPath; if (map == 0 || domain == 0) { return (0); } objPath = scat(myself, T, scat(myself, F, map, ","), scat(myself, F, domain, ".")); return (objPath); } /* * Convert string to __nis_value_t */ __nis_value_t *stringToValue(char *dptr, int dsize) { char *myself = "stringToValue"; char *emptystr = ""; __nis_value_t *val; if ((val = am(myself, sizeof (*val))) == 0) { return (0); } val->type = vt_string; val->repeat = 0; val->numVals = 1; if ((val->val = am(myself, sizeof (val->val[0]))) == 0) { sfree(val); return (0); } /* * Null strings or strings with length 0 are treated * as empty strings with length 1 */ if (dptr == 0 || dsize <= 0) { dptr = emptystr; dsize = 1; } val->val->length = dsize; if (dptr[dsize - 1] != '\0') { val->val->length = dsize + 1; } val->val->value = am(myself, val->val->length); if (val->val->value == 0) { freeValue(val, 1); return (0); } (void) memcpy(val->val->value, dptr, dsize); return (val); } /* * Returns an array of rule-values corresponding to the * splitfields. */ __nis_rule_value_t * processSplitField(__nis_table_mapping_t *sf, __nis_value_t *inVal, int *nv, int *statP) { char *sepset; __nis_rule_value_t *rvq; __nis_value_t **valA, *tempVal; int i, j, res, numVals, oldlen, count; char *str, *oldstr; /* sf will be non NULL */ if (inVal == 0 || inVal->type != vt_string) { *statP = MAP_PARAM_ERROR; return (0); } /* Get the separator list */ sepset = sf->separatorStr; /* Initialize rule-value */ rvq = 0; count = 0; if ((tempVal = stringToValue(inVal->val->value, inVal->val->length)) == 0) { *statP = MAP_NO_MEMORY; return (0); } str = oldstr = tempVal->val->value; oldlen = tempVal->val->length; while (str) { tempVal->val->value = str; tempVal->val->length = strlen(str) + 1; /* Loop to check which format matches str */ for (i = 0; i <= sf->numSplits; i++) { valA = matchMappingItem(sf->e[i].element.match.fmt, tempVal, &numVals, sepset, &str); if (valA == 0) { /* The format didn't match. Try the next one */ continue; } /* * If we are here means we had a match. * Each new set of values obtained from the match is * added to a new rule-value. This is to preserve the * the distinction between each set. */ rvq = growRuleValue(count, count + 1, rvq, 0); if (rvq == 0) { *statP = MAP_INTERNAL_ERROR; for (j = 0; j < numVals; j++) freeValue(valA[j], 1); sfree(valA); tempVal->val->value = oldstr; tempVal->val->length = oldlen; freeValue(tempVal, 1); return (0); } count++; for (j = 0; j < numVals; j++) { res = addCol2RuleValue(vt_string, sf->e[i].element.match.item[j].name, valA[j]->val->value, valA[j]->val->length, &rvq[count - 1]); if (res == -1) { *statP = MAP_INTERNAL_ERROR; for (; j < numVals; j++) freeValue(valA[j], 1); sfree(valA); tempVal->val->value = oldstr; tempVal->val->length = oldlen; freeValue(tempVal, 1); freeRuleValue(rvq, count); return (0); } freeValue(valA[j], 1); } sfree(valA); /* * Since we had a match, break out of this loop * to parse remainder of str */ break; } /* Didn't find any match, so get out of the loop */ if (i > sf->numSplits) { str = 0; break; } /* Skip the separators before looping back */ if (str) { str = str + strspn(str, sepset); if (*str == '\0') break; } } tempVal->val->value = oldstr; tempVal->val->length = oldlen; freeValue(tempVal, 1); if (str == 0) { freeRuleValue(rvq, count); return (0); } if (nv != 0) *nv = count; return (rvq); } /* * Convert the datum to an array of RuleValues */ __nis_rule_value_t * datumToRuleValue(datum *key, datum *value, __nis_table_mapping_t *t, int *nv, char *domain, bool_t readonly, int *statP) { __nis_rule_value_t *rvq, *subrvq, *newrvq; __nis_value_t *val; __nis_value_t **valA; __nis_table_mapping_t *sf; int valueLen, comLen, numVals, nr, count = 1; int i, j, k, l; char *ipaddr, *ipvalue; /* At this point, 't' is always non NULL */ /* Initialize rule-value */ if ((rvq = initRuleValue(1, 0)) == 0) { *statP = MAP_INTERNAL_ERROR; return (0); } /* Add domainname to rule-value */ if (addCol2RuleValue(vt_string, N2LDOMAIN, domain, strlen(domain), rvq)) { freeRuleValue(rvq, 1); *statP = MAP_INTERNAL_ERROR; return (0); } /* Handle key */ if (key != 0) { /* Add field=value pair for N2LKEY */ i = addCol2RuleValue(vt_string, N2LKEY, key->dptr, key->dsize, rvq); /* For readonly, add field=value pair for N2LSEARCHKEY */ if (readonly == TRUE && i == 0) { i = addCol2RuleValue(vt_string, N2LSEARCHKEY, key->dptr, key->dsize, rvq); } if (i) { freeRuleValue(rvq, 1); *statP = MAP_INTERNAL_ERROR; return (0); } /* Add field=value pairs for IP addresses */ if (checkIPaddress(key->dptr, key->dsize, &ipaddr) > 0) { /* If key is IPaddress, use preferred format */ ipvalue = ipaddr; valueLen = strlen(ipaddr); i = addCol2RuleValue(vt_string, N2LIPKEY, ipvalue, valueLen, rvq); } else { /* If not, use original value for N2LSEARCHIPKEY */ ipaddr = 0; ipvalue = key->dptr; valueLen = key->dsize; i = 0; } if (readonly == TRUE && i == 0) { i = addCol2RuleValue(vt_string, N2LSEARCHIPKEY, ipvalue, valueLen, rvq); } sfree(ipaddr); if (i) { freeRuleValue(rvq, 1); *statP = MAP_INTERNAL_ERROR; return (0); } } /* Handle datum value */ if (value != 0 && t->e) { valueLen = value->dsize; /* * Extract the comment, if any, and add it to * the rule-value. */ if (t->commentChar != '\0') { /* * We loop on value->dsize because value->dptr * may not be NULL-terminated. */ for (i = 0; i < value->dsize; i++) { if (value->dptr[i] == t->commentChar) { valueLen = i; comLen = value->dsize - i - 1; if (comLen == 0) break; if (addCol2RuleValue(vt_string, N2LCOMMENT, value->dptr + i + 1, comLen, rvq)) { freeRuleValue(rvq, 1); *statP = MAP_INTERNAL_ERROR; return (0); } break; } } } /* Skip trailing whitespaces */ for (; valueLen > 0 && (value->dptr[valueLen - 1] == ' ' || value->dptr[valueLen - 1] == '\t'); valueLen--); /* * At this point valueLen is the effective length of * the data. Convert value into __nis_value_t so that * we can use the matchMappingItem function to break it * into fields. */ if ((val = stringToValue(value->dptr, valueLen)) == 0) { freeRuleValue(rvq, 1); *statP = MAP_NO_MEMORY; return (0); } /* Perform namefield match */ valA = matchMappingItem(t->e->element.match.fmt, val, &numVals, 0, 0); if (valA == 0) { freeValue(val, 1); freeRuleValue(rvq, 1); *statP = MAP_NAMEFIELD_MATCH_ERROR; return (0); } /* We don't need val anymore, so free it */ freeValue(val, 1); /* * Since matchMappingItem only returns us an array of * __nis_value_t's, we need to associate each value * in the array with the corresponding item name. * This code assumes that numVals will be less than or * equal to the number of item names associated with * the format. * These name=value pairs are added to rvq. */ for (i = 0, *statP = SUCCESS; i < numVals; i++) { for (j = 0; j < count; j++) { if (addCol2RuleValue(vt_string, t->e->element.match.item[i].name, valA[i]->val->value, valA[i]->val->length, &rvq[j])) { *statP = MAP_INTERNAL_ERROR; break; } } if (*statP == MAP_INTERNAL_ERROR) break; /* * Check if splitField exists for the field. * Since splitfields are also stored as mapping * structures, we need to get the hash table entry * corresponding to the splitfield name */ sf = mappingFromMap(t->e->element.match.item[i].name, domain, statP); if (*statP == MAP_NO_MEMORY) break; *statP = SUCCESS; if (sf == 0) continue; /* * Process and add splitFields to rule-value rvq */ subrvq = processSplitField(sf, valA[i], &nr, statP); if (subrvq == 0) { /* statP would have been set */ break; } /* * We merge 'count' rule-values in rvq with 'nr' * rule-values from subrvq to give us a whopping * 'count * nr' rule-values */ /* Initialize the new rule-value array */ if ((newrvq = initRuleValue(count * nr, 0)) == 0) { *statP = MAP_INTERNAL_ERROR; freeRuleValue(subrvq, nr); break; } for (j = 0, l = 0; j < nr; j++) { for (k = 0; k < count; k++, l++) { if ((mergeRuleValue(&newrvq[l], &rvq[k]) == -1) || (mergeRuleValue( &newrvq[l], &subrvq[j]) == -1)) { *statP = MAP_INTERNAL_ERROR; for (i = 0; i < numVals; i++) freeValue(valA[i], 1); sfree(valA); freeRuleValue(rvq, count); freeRuleValue(newrvq, count * nr); freeRuleValue(subrvq, nr); return (0); } } } freeRuleValue(rvq, count); rvq = newrvq; count = l; freeRuleValue(subrvq, nr); } /* We don't need valA anymore, so free it */ for (i = 0; i < numVals; i++) freeValue(valA[i], 1); sfree(valA); if (*statP != SUCCESS) { freeRuleValue(rvq, count); return (0); } } /* if value */ if (nv != 0) *nv = count; return (rvq); } /* * Generate name=values pairs for splitfield names * * Consider Example: * nisLDAPnameFields club: * ("%s %s %s", name, code, members) * nisLDAPsplitField members: * ("(%s,%s,%s)", host, user, domain), * ("%s", group) * On entry, * - rv is an array of numVals rule-values each containing * name=value pairs for names occuring in nisLDAPsplitField. * (i.e host, user, domain, group) * - trv contains name=value pairs for names occuring in * nisLDAPnameFields. (i.e name, code but not members) * * For every name in nisLDAPnamefields that is a splitfield, * this function applies the data in rv to the corresponding * splitfield formats (accessed thru t), to generate a single * string value for the corresponding splitfield (members). * This new name=value pair is then added to trv. * Besides, any uninitialized namefield names are set to empty strings. */ suc_code addSplitFieldValues(__nis_table_mapping_t *t, __nis_rule_value_t *rv, __nis_rule_value_t *trv, int numVals, char *domain) { __nis_table_mapping_t *sf; __nis_value_t *val; int i, j, k, nitems, res, statP; char *str, *tempstr; char delim[2] = {0, 0}; char *emptystr = ""; char *myself = "addSplitFieldValues"; if (trv == 0) return (MAP_INTERNAL_ERROR); if (t->e == 0) return (SUCCESS); nitems = t->e->element.match.numItems; /* * Procedure: * - Check each name in nisLDAPnamefield * - if it's a splifield, construct its value and add it to trv * - if not, check if it has a value * - if not, add empty string */ for (i = 0, sf = 0; i < nitems; i++) { if (rv) { /* * str will eventually contain the single string * value for the corresponding splitfield. * No point initializing str if rv == 0 because * splitfield cannot be constructed without rv. * So, only initialized here. */ str = 0; /* Check if it's a splitfield name */ sf = mappingFromMap(t->e->element.match.item[i].name, domain, &statP); /* * Return only incase of memory allocation failure. * The other error case (MAP_NO_MAPPING_EXISTS), * indicates that the item name is not a splitfieldname * i.e it's a namefieldname. This case is handled by * the following if (sf == 0) */ if (statP == MAP_NO_MEMORY) return (statP); } if (sf == 0) { /* * Not a splitfield name. Verify if it has a value */ if (findVal(t->e->element.match.item[i].name, trv, mit_nisplus) == 0) { /* if not, use empty string */ res = addCol2RuleValue(vt_string, t->e->element.match.item[i].name, emptystr, 0, trv); if (res == -1) { return (MAP_INTERNAL_ERROR); } } /* * If rv == 0 then sf == 0 so we will continue here * i.e. does not matter that str is not yet set up. */ continue; } /* Code to construct a single value */ /* Use the first separator character as the delimiter */ delim[0] = sf->separatorStr[0]; for (j = 0; j < numVals; j++) { /* sf->numSplits is zero-based */ for (k = 0; k <= sf->numSplits; k++) { val = getMappingFormatArray( sf->e[k].element.match.fmt, &rv[j], fa_item, sf->e[k].element.match.numItems, sf->e[k].element.match.item); if (val == 0) continue; if (val->numVals > 0) { if (str) { tempstr = scat(myself, 0, str, delim); sfree(str); if (tempstr) str = tempstr; else { freeValue(val, 1); return (MAP_NO_MEMORY); } } tempstr = scat(myself, 0, str, val->val->value); sfree(str); if (tempstr) str = tempstr; else { freeValue(val, 1); return (MAP_NO_MEMORY); } } freeValue(val, 1); } } if (str == 0) str = emptystr; res = addCol2RuleValue(vt_string, t->e->element.match.item[i].name, str, strlen(str), trv); if (str != emptystr) sfree(str); if (res == -1) { return (MAP_INTERNAL_ERROR); } } return (SUCCESS); } /* * Updates 'rv' with NIS name=value pairs suitable to * construct datum from namefield information. * Some part based on createNisPlusEntry (from ldap_nisdbquery.c) * This code assumes that from a given LDAP entry, applying the * mapping rules, would give us one or more NIS entries, differing * only in key. */ suc_code buildNISRuleValue(__nis_table_mapping_t *t, __nis_rule_value_t *rv, char *domain) { int r, i, j, k, l, nrq, res, len; int numItems, splitname, count, statP; __nis_value_t *rval; __nis_mapping_item_t *litem; __nis_mapping_rule_t *rl; __nis_rule_value_t *rvq; char *value, *emptystr = ""; statP = SUCCESS; /* Initialize default base */ __nisdb_get_tsd()->searchBase = t->objectDN->read.base; /* Initialize rule-value rvq */ rvq = 0; count = 0; /* Add domainname to rule-value */ if (addCol2RuleValue(vt_string, N2LDOMAIN, domain, strlen(domain), rv)) { return (MAP_INTERNAL_ERROR); } for (r = 0; r < t->numRulesFromLDAP; r++) { rl = t->ruleFromLDAP[r]; /* Set escapeFlag if RHS is "dn" to remove escape chars */ if (rl->rhs.numElements == 1 && rl->rhs.element->type == me_item && rl->rhs.element->element.item.type == mit_ldap && strcasecmp(rl->rhs.element->element.item.name, "dn") == 0) { __nisdb_get_tsd()->escapeFlag = '2'; } rval = buildRvalue(&rl->rhs, mit_ldap, rv, NULL); /* Reset escapeFlag */ __nisdb_get_tsd()->escapeFlag = '\0'; if (rval == 0) { continue; } if (rval->numVals <= 0) { /* Treat as invalid */ freeValue(rval, 1); continue; } litem = buildLvalue(&rl->lhs, &rval, &numItems); if (litem == 0) { /* This will take care of numItems == 0 */ freeValue(rval, 1); continue; } if (rval->numVals > 1) { if (numItems == 1 && litem->repeat) nrq = rval->numVals; else if (numItems > 1 && rval->repeat) nrq = 1 + ((rval->numVals-1)/numItems); else nrq = 1; } else nrq = 1; /* Set splitname if splitfield names are specified */ for (i = 0; i < numItems; i++) { if (strcasecmp(litem[i].name, N2LKEY) == 0 || strcasecmp(litem[i].name, N2LIPKEY) == 0 || strcasecmp(litem[i].name, N2LCOMMENT) == 0) continue; for (j = 0; j < t->numColumns; j++) { if (strcmp(litem[i].name, t->column[j]) == 0) break; } if (j == t->numColumns) break; } splitname = (i < numItems)?1:0; for (j = 0; j < nrq; j++) { if (splitname == 1) { /* * Put every value of splitfieldname in a new * rule-value. Helps generating splitfields. */ rvq = growRuleValue(count, count + 1, rvq, 0); if (rvq == 0) { freeRuleValue(rvq, count); freeValue(rval, 1); freeMappingItem(litem, numItems); return (MAP_INTERNAL_ERROR); } count++; } for (k = j % nrq, l = 0; l < numItems; k += nrq, l++) { /* If we run out of values, use empty strings */ if (k >= rval->numVals) { value = emptystr; len = 0; } else { value = rval->val[k].value; len = rval->val[k].length; } res = (splitname == 1)?addCol2RuleValue( vt_string, litem[l].name, value, len, &rvq[count - 1]):0; if (res != -1) res = addCol2RuleValue(vt_string, litem[l].name, value, len, rv); if (res == -1) { freeRuleValue(rvq, count); freeValue(rval, 1); freeMappingItem(litem, numItems); return (MAP_INTERNAL_ERROR); } } } freeValue(rval, 1); rval = 0; freeMappingItem(litem, numItems); litem = 0; numItems = 0; } /* for r < t->numRulesFromLDAP */ statP = addSplitFieldValues(t, rvq, rv, count, domain); if (rvq) freeRuleValue(rvq, count); if (verifyIndexMatch(t, 0, rv, 0, 0) == 0) return (MAP_INDEXLIST_ERROR); return (statP); } /* end of buildNISRuleValue */ /* * Convert rule-value to datum using namefield information */ datum * ruleValueToDatum(__nis_table_mapping_t *t, __nis_rule_value_t *rv, int *statP) { __nis_value_t *val; datum *value; char *str, *cstr, commentSep[3] = {' ', 0, 0}; char *myself = "ruleValueToDatum"; /* No error yet */ *statP = 0; /* Return empty datum if no namefield information available */ if (t->e == 0) { if ((value = am(myself, sizeof (*value))) == 0) *statP = MAP_NO_MEMORY; return (value); } val = getMappingFormatArray(t->e->element.match.fmt, rv, fa_item, t->e->element.match.numItems, t->e->element.match.item); if (val && val->val && val->val->value) { if ((value = am(myself, sizeof (*value))) == 0) { *statP = MAP_NO_MEMORY; freeValue(val, 1); return (0); } /* Strip trailing whitespaces */ cstr = (char *)val->val->value + val->val->length; for (; cstr >= (char *)val->val->value && (*cstr == ' ' || *cstr == '\t'); *cstr-- = '\0'); if (t->commentChar != '\0' && (str = findVal(N2LCOMMENT, rv, mit_nisplus)) != 0 && *str != '\0') { commentSep[1] = t->commentChar; cstr = scat(myself, F, commentSep, str); if (cstr) { value->dptr = scat(myself, F, val->val->value, cstr); sfree(cstr); } } else { value->dptr = sdup(myself, T, val->val->value); } freeValue(val, 1); if (value->dptr) { value->dsize = strlen(value->dptr); return (value); } else { *statP = MAP_NO_MEMORY; sfree(value); return (0); } } *statP = MAP_NAMEFIELD_MATCH_ERROR; return (0); } datum * getKeyFromRuleValue(__nis_table_mapping_t *t, __nis_rule_value_t *rv, int *nv, int *statP, bool_t xlate_to_lcase) { int i, j, k; datum *key = 0; char *str; char *myself = "getKeyFromRuleValue"; /* No error yet */ *statP = 0; if (rv == 0 || nv == 0) return (0); for (i = 0; i < rv->numColumns; i++) { if (rv->colName[i] == 0) continue; if (strcasecmp(N2LKEY, rv->colName[i]) == 0 || strcasecmp(N2LIPKEY, rv->colName[i]) == 0) { if ((*nv = rv->colVal[i].numVals) == 0) return (0); if ((key = am(myself, sizeof (key[0]) * *nv)) == 0) { *statP = MAP_NO_MEMORY; return (0); } for (j = 0; j < *nv; j++) { if ((str = rv->colVal[i].val[j].value) == 0) { key[j].dsize = 0; key[j].dptr = 0; } else { if (verifyIndexMatch(t, 0, 0, rv->colName[i], str) == 0) { key[j].dsize = 0; key[j].dptr = 0; continue; } key[j].dsize = strlen(str); key[j].dptr = am(myself, key[j].dsize + 1); if (key[j].dptr == 0) { *statP = MAP_NO_MEMORY; for (--j; j >= 0; j--) sfree(key[j].dptr); sfree(key); return (0); } /* transliterate key to lowercase */ if (xlate_to_lcase == TRUE) { /* * For multi-homed * entries, skip over * "YP_MULTI_" prefix. */ k = 0; if (strncmp(YPMULTI, str, YPMULTISZ) == 0) { k = YPMULTISZ; bcopy(str, key[j].dptr, YPMULTISZ); } while (k < key[j].dsize) { key[j].dptr[k] = (char)tolower( (int)(uchar_t) str[k]); k++; } } else { bcopy(str, key[j].dptr, key[j].dsize); } } } return (key); } } return (0); } /* * Get the mapping structure corresponding to `map,domain.' */ __nis_table_mapping_t * mappingFromMap(char *map, char *domain, int *statP) { char *mapPath; __nis_table_mapping_t *t; /* No error yet */ *statP = 0; /* Construct map,domain. */ if ((mapPath = getFullMapName(map, domain)) == 0) { *statP = MAP_NO_MEMORY; return (0); } /* Get the hash table entry for the mapPath */ if ((t = __nis_find_item_mt(mapPath, &ldapMappingList, 1, 0)) == 0) { *statP = MAP_NO_MAPPING_EXISTS; } sfree(mapPath); return (t); } /* * Verify at least one key value obtained from DIT matches the search key * RETURNS: 1 MATCH * 0 NO MATCH * -1 NO KEY FOUND */ static int verifyKey(char *key, __nis_rule_value_t *rv) { int i, j; char *sipkey, *str; for (i = 0; i < rv->numColumns; i++) { if (rv->colName[i] == 0) continue; if (strcasecmp(N2LKEY, rv->colName[i]) == 0) { if (rv->colVal[i].val == 0) return (0); for (j = 0; j < rv->colVal[i].numVals; j++) { str = (char *)rv->colVal[i].val[j].value; if (str && strcmp(str, key) == 0) return (1); } return (0); } else if (strcasecmp(N2LIPKEY, rv->colName[i]) == 0) { if (checkIPaddress(key, strlen(key), &sipkey) > 0) { if (rv->colVal[i].val == 0) return (0); for (j = 0; j < rv->colVal[i].numVals; j++) { str = rv->colVal[i].val[j].value; if (str && strcmp(str, sipkey) == 0) { sfree(sipkey); return (1); } } sfree(sipkey); } return (0); } } return (-1); } /* * Read (i.e get and map) a single NIS entry from the LDAP DIT */ bool_t singleReadFromDIT(char *map, char *domain, datum *key, datum *value, int *statP) { __nis_table_mapping_t *t; __nis_rule_value_t *rv_request = 0, *rv_result = 0; __nis_ldap_search_t *ls; __nis_object_dn_t *objectDN = NULL; int i, rc, nr = 0; datum *datval = 0; char *skey, *str; char *myself = "singleReadFromDIT"; *statP = SUCCESS; if (!map || !domain || !key || !value) { *statP = MAP_PARAM_ERROR; return (FALSE); } /* Get the mapping information for the map */ if ((t = mappingFromMap(map, domain, statP)) == 0) { /* * No problem. We don't handle this map and domain. Maybe it's * handled by a service other than NIS. */ return (FALSE); } /* NULL-terminated version of datum key for logging */ if ((skey = am(myself, key->dsize + 1)) == 0) { *statP = MAP_NO_MEMORY; return (FALSE); } (void) memcpy(skey, key->dptr, key->dsize); if ((str = getFullMapName(map, domain)) == 0) { *statP = MAP_NO_MEMORY; return (FALSE); } /* For each alternate mapping */ for (; t != 0; t = t->next) { /* Verify objName */ if (strcmp(str, t->objName) != 0) { continue; } /* Verify if key matches the index */ if (verifyIndexMatch(t, 0, 0, N2LKEY, skey) == 0 || verifyIndexMatch(t, 0, 0, N2LIPKEY, skey) == 0) continue; /* Check if rulesFromLDAP are provided */ if (t->numRulesFromLDAP == 0) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: No rulesFromLDAP information available " "for %s (%s)", myself, t->dbId, map); continue; } /* Convert key into rule-value */ if ((rv_request = datumToRuleValue(key, 0, t, 0, domain, TRUE, statP)) == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Conversion error %d (NIS to name=value " "pairs) for NIS key (%s) for %s (%s)", myself, *statP, skey, t->dbId, map); continue; } /* Convert rule-value into ldap request */ for (objectDN = t->objectDN; objectDN && objectDN->read.base; objectDN = objectDN->next) { ls = createLdapRequest(t, rv_request, 0, 1, NULL, objectDN); if (ls == 0) { *statP = MAP_CREATE_LDAP_REQUEST_ERROR; logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Failed to create ldapSearch " "request for " "NIS key (%s) for %s (%s) " "for base %s", myself, skey, t->dbId, map, objectDN->read.base); continue; } ls->timeout.tv_sec = SINGLE_ACCESS_TIMEOUT_SEC; ls->timeout.tv_usec = SINGLE_ACCESS_TIMEOUT_USEC; /* Query LDAP */ nr = (ls->isDN)?0:-1; rv_result = ldapSearch(ls, &nr, 0, statP); freeLdapSearch(ls); if (rv_result == 0) { if (*statP == LDAP_NO_SUCH_OBJECT) { /* Entry does not exist in */ /* the ldap server */ } continue; } freeRuleValue(rv_request, 1); rv_request = 0; /* if result > 1, first match will be returned */ if (nr > 1) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: %d ldapSearch results " "for NIS key (%s) " "for %s (%s) for base %s. " "First match will be returned ", myself, nr, skey, t->dbId, map, objectDN->read.base); } for (i = 0; i < nr; i++) { /* Convert LDAP data to NIS equivalents */ *statP = buildNISRuleValue(t, &rv_result[i], domain); if (*statP == MAP_INDEXLIST_ERROR) continue; if (*statP != SUCCESS) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Conversion error %d (LDAP to " "name=value pairs) for NIS key (%s) " "for %s (%s) for base %s", myself, *statP, skey, t->dbId, map, objectDN->read.base); continue; } /* * Check if 'key' from the ldap result matches the key * provided by our caller */ if ((rc = verifyKey(skey, &rv_result[i])) == -1) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: Cannot verify key from ldap " "result for NIS key (%s) for %s (%s) " "for base %s", myself, skey, t->dbId, map, objectDN->read.base); continue; } if (rc == 1) { datval = ruleValueToDatum(t, &rv_result[i], statP); if (datval == 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Conversion error %d " "(name=value pairs to NIS) " "for NIS key (%s) for %s (%s)" " for base %s", myself, *statP, skey, t->dbId, map, objectDN->read.base); continue; } if (value) { value->dptr = datval->dptr; value->dsize = datval->dsize; } sfree(datval); sfree(skey); freeRuleValue(rv_result, nr); rv_result = 0; *statP = SUCCESS; /* Free full map name */ sfree(str); return (TRUE); } } freeRuleValue(rv_result, nr); rv_result = 0; } /* end of for over objectDN */ if (rv_request != 0) { freeRuleValue(rv_request, 1); rv_request = 0; } if (rv_result != 0) { freeRuleValue(rv_result, nr); rv_result = 0; } } sfree(skey); *statP = MAP_NO_MATCHING_KEY; /* Free full map name */ sfree(str); return (FALSE); } /* * Maps and writes a single NIS entry to the LDAP DIT */ int singleWriteToDIT(char *map, char *domain, datum *key, datum *value, bool_t replace) { __nis_table_mapping_t *t; __nis_rule_value_t *rv, *frv; __nis_ldap_search_t *ls; int statP = SUCCESS, flag; int nv, nr, i, rc, collapse; char *dn = 0, *skey, *svalue, *str; char *myself = "singleWriteToDIT"; if (!map || !domain || !key || !value) { return (MAP_PARAM_ERROR); } /* Return SUCCESS for empty or whitespace key */ for (i = 0; i < key->dsize && (key->dptr[i] == 0 || key->dptr[i] == ' ' || key->dptr[i] == '\t'); i++); if (i >= key->dsize) return (SUCCESS); /* Get the mapping information for the map */ if ((t = mappingFromMap(map, domain, &statP)) == 0) { /* * No problem. We don't handle this map and domain. Maybe it's * handled by a service other than NIS. */ return (statP); } /* NULL-terminated version of key and value for logging */ if ((skey = am(myself, key->dsize + 1)) == 0) return (MAP_NO_MEMORY); (void) memcpy(skey, key->dptr, key->dsize); if ((svalue = am(myself, value->dsize + 1)) == 0) { sfree(skey); return (MAP_NO_MEMORY); } (void) memcpy(svalue, value->dptr, value->dsize); if ((str = getFullMapName(map, domain)) == 0) { sfree(skey); sfree(svalue); return (MAP_NO_MEMORY); } /* For each alternate mapping */ for (flag = 0; t != 0; t = t->next) { /* Verify objName */ if (strcmp(str, t->objName) != 0) { continue; } /* Verify if key matches the index */ if (verifyIndexMatch(t, 0, 0, N2LKEY, skey) == 0 || verifyIndexMatch(t, 0, 0, N2LIPKEY, skey) == 0) continue; /* Check the writespecs */ if (t->objectDN->write.base == 0) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: No baseDN in writespec. Write disabled " "for %s (%s)", myself, t->dbId, map); continue; } /* Check if rulesToLDAP are provided */ if (t->numRulesToLDAP == 0) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: No rulesToLDAP. Write disabled for " "%s (%s)", myself, t->dbId, map); continue; } /* Set flag to indicate write is enabled */ flag = 1; /* Convert key and value into an array of rule-values */ if ((rv = datumToRuleValue(key, value, t, &nv, domain, FALSE, &statP)) == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Conversion error %d (NIS to name=value " "pairs) for NIS data (key=%s, value=%s) " "for %s (%s)", myself, statP, skey, svalue, t->dbId, map); sfree(skey); sfree(svalue); /* Free full map name */ sfree(str); return (statP); } /* Convert NIS data to LDAP equivalents for each rule-value */ for (i = 0; i < nv; i++) { /* Verify indexlist with name=value pairs */ if (verifyIndexMatch(t, 0, &rv[i], 0, 0) == 0) break; /* Create LDAP request and LDAP name=value pairs */ if ((ls = createLdapRequest(t, &rv[i], 0, 0, NULL, NULL)) == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Conversion error (name=value pairs" " to LDAP) for NIS data " "(key=%s, value=%s) for %s (%s)", myself, skey, svalue, t->dbId, map); freeRuleValue(rv, nv); sfree(skey); sfree(svalue); /* Free full map name */ sfree(str); return (MAP_CREATE_LDAP_REQUEST_ERROR); } freeLdapSearch(ls); /* printRuleValue(&rv[i]); */ } /* If i < nv then this alternate mapping isn't the one */ if (i < nv) continue; /* * Merge rule-values with the same DN so that we have * one ldap write request for each DN */ nr = nv; frv = mergeRuleValueWithSameDN(rv, &nr); freeRuleValue(rv, nv); if (frv == 0) { if (nr == -1) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Unable to merge LDAP write " "requests to same DN for NIS data " "(key=%s, value=%s) for %s (%s)", myself, skey, svalue, t->dbId, map); statP = MAP_INTERNAL_ERROR; } else if (nr == 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Cannot generate write DN due to " "missing information for NIS data " "(key=%s, value=%s) for %s (%s)", myself, skey, svalue, t->dbId, map); statP = MAP_NO_DN; } sfree(skey); sfree(svalue); /* Free full map name */ sfree(str); return (statP); } /* Write to the LDAP server */ for (collapse = 0, i = 0; i < nr; i++) { if ((dn = findVal("dn", &frv[i], mit_ldap)) != 0) { if (replace == FALSE) { /* ldap add */ rc = ldapAdd(dn, &frv[i], t->objectDN->write.attrs, 0); } else { /* ldap modify with addFirst set */ rc = ldapModify(dn, &frv[i], t->objectDN->write.attrs, 1); } /* if we get err=20, collapse and try again */ if (!collapse && (rc == LDAP_TYPE_OR_VALUE_EXISTS) && (collapseRuleValue(&frv[i]) == 1)) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Ignoring values differing " "in case from NIS data (key=%s," " value=%s) for (dn: %s) for " "%s (%s)", myself, skey, svalue, dn, t->dbId, map); collapse = 1; i--; continue; } collapse = 0; if (rc != LDAP_SUCCESS) { /* Log error */ logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: %s error %d (%s) for " "(dn: %s) for NIS data " "(key=%s, value=%s) " "for %s (%s)", myself, (replace == TRUE) ? "ldapModify" : "ldapAdd", rc, ldap_err2string(rc), dn, skey, svalue, t->dbId, map); /* Dumping failed call may be useful */ /* printRuleValue(&frv[i]); */ /* * Return the error code and let wrapper * sort out if mapping should continue * or abort. */ statP = rc; sfree(skey); sfree(svalue); freeRuleValue(frv, nr); /* Free full map name */ sfree(str); return (statP); } } } freeRuleValue(frv, nr); } sfree(skey); sfree(svalue); /* Free full map name */ sfree(str); return ((flag)?SUCCESS:MAP_WRITE_DISABLED); } suc_code collapseRuleValue(__nis_rule_value_t *rv) { int i, j, k, flag; /* Using 'val' to appease cstyle's 80 chars/line limit */ __nis_value_t *val; for (i = 0, flag = 0; i < rv->numAttrs; i++) { val = &rv->attrVal[i]; for (j = 1; j < val->numVals; j++) { for (k = 0; k < j; k++) { if (val->val[j].length != val->val[k].length) continue; if (val->val[k].length == 0) continue; if (strncasecmp(val->val[j].value, val->val[k].value, val->val[j].length) != 0) continue; flag = 1; sfree(val->val[j].value); #ifdef ORDER_NOT_IMPORTANT val->val[j--] = val->val[--val->numVals]; #else /* Order needs to be maintained */ for (k = j + 1; k < val->numVals; k++) val->val[k - 1] = val->val[k]; j--; val->numVals--; #endif break; } } } return (flag); } /* ObjectClass lookup table */ static struct { const char *attrType; const char *objectClass; } oc_lookup[] = { { "o", "objectclass=organization"}, { "organizationname", "objectclass=organization"}, { "2.5.4.10", "objectclass=organization"}, { "ou", "objectclass=organizationalunit"}, { "organizationalunitname", "objectclass=organizationalunit"}, { "2.5.4.11", "objectclass=organizationalunit"}, { "c", "objectclass=country"}, { "countryname", "objectclass=country"}, { "2.5.4.6", "objectclass=country"}, { "dc", "objectclass=domain"}, { "domaincomponent", "objectclass=domain"}, { "0.9.2342.19200300.100.1.25", "objectclass=domain"}, { "nismapname", "objectclass=nismap"}, { "1.3.6.1.1.1.1.26", "objectclass=nismap"}, { "automountmapname", "objectclass=automountmap"}, { "1.3.6.1.1.1.1.31", "objectclass=automountmap"}, { 0, 0} }; /* * Returns the name of the objectclass to which the object * represented by the given 'rdn' will most likely belong to. * The return value is in static memory so it should not be * freed */ const char * getObjectClass(char *rdn) { char *attrtype, *p; int len, i; /* Skip leading whitespaces */ for (p = rdn; *p == ' ' || *p == '\t'; p++); if (*p == '\0') return (0); attrtype = p; /* Find '=' */ if ((p = strchr(attrtype, '=')) == 0 || p == attrtype || *(p - 1) == '\\') return (0); /* * Skip trailing whitespaces in attrtype * Don't worry, p won't decrease beyond attrtype */ for (--p; *p == ' ' || *p == '\t'; p--); len = p - attrtype + 1; for (i = 0; oc_lookup[i].attrType; i++) if (!strncasecmp(oc_lookup[i].attrType, attrtype, len)) /* Check length is right */ if (len == strlen(oc_lookup[i].attrType)) return (oc_lookup[i].objectClass); return (0); } /* * Split 'dn' into rdn and parentdn based on the first * occurrence of unescaped 'comma' or 'semicolon'. rdn * lies on the LHS while parentdn lies on the RHS of the * split. If none found, then an empty string ("") is * assigned to parentdn */ int splitDN(char *dn, char **rdn, char **parentdn) { char *value, *name; char *myself = "splitDN"; if ((name = sdup(myself, T, dn)) == 0) return (-1); for (value = name; *value != '\0'; value++) { if (*value == ',' || *value == ';') if (value == name || *(value - 1) != '\\') break; } if (*value != '\0') { *value = '\0'; value++; } else value = 0; if (parentdn) { if ((*parentdn = sdup(myself, T, value)) == 0) { sfree(name); return (-1); } } if (rdn) *rdn = name; else sfree(name); return (1); } /* * FUNCTION : makeNISObject() * * DESCRIPTION: Sets up a nis Object in the DIT. * * GIVEN : * Case 1: Both 'domain' and 'dn' are non-NULL * Create nisDomainObject with the given information * Case 2: Only 'domain' is non-NULL * Obtain the 'dn' from the nisLDAPdomainContext list * Create nisDomainObject with the above information * Case 3: Only 'dn' is non-NULL * Create an object with the 'dn' * Here we guess the objectclass attribute, based on * oc_lookup table * Case 4: Both 'domain' and 'dn' are NULL * Error * * RETURNS : SUCCESS = It worked * FAILURE = There was a problem. */ suc_code makeNISObject(char *domain, char *dn) { __nis_rule_value_t *rv; __nis_ldap_search_t *ls; int i, rc, nr, add_rc; char *val; char *myself = "makeNISObject"; if (!dn && !domain) return (FAILURE); /* * If only 'domain' name is provided, then * try to find dn from the nisLDAPdomainContext * list generated by the parser */ if (!dn) { for (i = 0; i < ypDomains.numDomains; i++) { if (ypDomains.domainLabels[i] == 0) continue; if (strcasecmp(domain, ypDomains.domainLabels[i]) == 0) { dn = ypDomains.domains[i]; break; } } if (!dn) return (FAILURE); } /* * If only 'dn' is given, then it means that the * caller simply wants to a create an entry for * that 'dn'. * * If 'domain' is given, then check if the 'dn' * has already been set up as a nis domain object. * If not, see if we can make it become one. */ if (domain) { /* * Check to see if the nis domain object has * already been set up */ ls = buildLdapSearch(dn, LDAP_SCOPE_BASE, 0, 0, "objectclass=*", 0, 0, 0); if (ls == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Unable to create ldapSearch " "request for dn: %s", myself, dn); return (FAILURE); } nr = -1; rv = ldapSearch(ls, &nr, 0, &rc); freeLdapSearch(ls); if (rc == LDAP_SUCCESS) { val = findVal("nisDomain", rv, mit_ldap); if (val != NULL) { /* * Yes, nis domain object found. Check * to see if the domain names match. * If so, we are done. If not, log * a warning message, and return SUCCESS. */ if (strcasecmp(val, domain) == 0) { freeRuleValue(rv, nr); return (SUCCESS); } else { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Entry (dn: %s) already " "contains a nis domain name " "(%s). The domain name (%s) " "is not added.", myself, dn, val, domain); freeRuleValue(rv, nr); return (SUCCESS); } } else { freeRuleValue(rv, nr); /* * Entry for the 'dn' exists, but it * is not a nis domain object yet. * Add the nisDoamin attribute and * the nisDomainObject objectclass to * the entry. */ if ((rv = initRuleValue(1, 0)) == 0) return (FAILURE); if (addSAttr2RuleValue("nisDomain", domain, rv) == -1) { freeRuleValue(rv, 1); return (FAILURE); } rc = ldapModify(dn, rv, "objectclass=nisDomainObject", 0); freeRuleValue(rv, 1); if (rc == LDAP_SUCCESS) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: entry (dn: %s) " "modified to be an " "nis domain object", myself, dn); return (SUCCESS); } else { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: unable to modify " "entry (dn: %s) to be " "a nis domain object: " "ldapModify error %d (%s)", myself, dn, rc, ldap_err2string(rc)); return (FAILURE); } } } else { /* search for 'dn' failed */ freeRuleValue(rv, nr); /* * It is OK if no such object, otherwise * log an error. */ if (rc != LDAP_NO_SUCH_OBJECT) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: unable to retrieve " "entry (dn: %s): " "ldapSearch error %d (%s)", myself, dn, rc, ldap_err2string(rc)); return (FAILURE); } } /* * If the 'dn' is actually the naming context of * the DIT, we should be able to make it a nis domain * object without worrying about missing parent * entries. If unable to add the entry for the 'dn' * due to missing parent entries, fall through * to create them and then add the nis domain object. */ if (addNISObject(domain, dn, &add_rc) == SUCCESS) return (SUCCESS); else if (add_rc != LDAP_NO_SUCH_OBJECT) return (FAILURE); } /* Create parent */ if (addParent(dn, NULL) == FAILURE) return (FAILURE); if (addNISObject(domain, dn, NULL) == FAILURE) return (FAILURE); return (SUCCESS); } suc_code addParent(char *dn, char **attr) { __nis_rule_value_t *rv; __nis_ldap_search_t *ls; int rc, nr; char *parentdn = 0, *rdn = 0; char *myself = "addParent"; /* Obtain parentdn */ if (splitDN(dn, &rdn, &parentdn) == -1) return (FAILURE); if (!parentdn) { sfree(rdn); return (FAILURE); } /* Check if parentdn exists */ ls = buildLdapSearch(parentdn, LDAP_SCOPE_BASE, 0, 0, "objectclass=*", 0, 0, 0); if (ls == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Unable to create ldapSearch request for " "parent (dn: %s) of (dn: %s)", myself, parentdn, dn); sfree(parentdn); sfree(rdn); return (FAILURE); } nr = -1; rv = ldapSearch(ls, &nr, 0, &rc); freeLdapSearch(ls); freeRuleValue(rv, nr); /* Create parent if it doesn't exists */ if (rc == LDAP_NO_SUCH_OBJECT) { if (makeNISObject(0, parentdn) == FAILURE) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Unable to create parent (dn: %s) of " "(dn: %s) in the DIT", myself, parentdn, dn); sfree(parentdn); sfree(rdn); return (FAILURE); } } sfree(parentdn); if (attr && rdn) *attr = (char *)getObjectClass(rdn); sfree(rdn); return (SUCCESS); } /* * FUNCTION : is_fatal_error() * * DESCRIPTION: Works out if a failed mapping operation should be retried. * * INPUTS : Result code from operation * * OUTPUTS : TRUE = Fatal error, don't retry. * FALSE = Temporary error, retry. */ bool_t is_fatal_error(int res) { if (0 > res) /* An internal mapping error. Not going to go away. */ return (TRUE); switch (res) { case (LDAP_PROTOCOL_ERROR): case (LDAP_TIMELIMIT_EXCEEDED): case (LDAP_PARTIAL_RESULTS): case (LDAP_BUSY): case (LDAP_UNAVAILABLE): case (LDAP_UNWILLING_TO_PERFORM): case (LDAP_OTHER): case (LDAP_SERVER_DOWN): case (LDAP_LOCAL_ERROR): case (LDAP_TIMEOUT): case (LDAP_NO_MEMORY): /* Probably worth a retry */ return (FALSE); default: return (TRUE); } } /* * FUNCTION : addNISObject() * * DESCRIPTION: Add a nis Object in the DIT. * * GIVEN : * Case 1: 'dn' is NULL * Error * Case 2: 'domain' is non-NULL * Create nisDomainObject with the given information * Case 3: 'domain' is NULL * Create an object with the 'dn' * Here we guess the objectclass attribute, based on * oc_lookup table * * RETURNS : SUCCESS = It worked * FAILURE = There was a problem. If the ldap add * operation failed, ldap_rc will be set * to the ldap error code. */ suc_code addNISObject(char *domain, char *dn, int *ldap_rc) { __nis_rule_value_t *rv; int rc; char *objClassAttrs = NULL, *attrs; char *value, *svalue, *rdn = NULL; char *myself = "addNISObject"; if (!dn) return (FAILURE); if ((rv = initRuleValue(1, 0)) == 0) return (FAILURE); if (ldap_rc) *ldap_rc = -1; /* * Add name=value pairs from RDN. Although this is not required * for SunOne Directory Server, during openldap interoperabilty * tests, it was found out that openldap server returned object * class violation errors if MUST attributes were not specified * explicitly. */ if (splitDN(dn, &rdn, 0) == -1) return (FAILURE); if (rdn != NULL) { objClassAttrs = (char *)getObjectClass(rdn); if (objClassAttrs == NULL) { sfree(rdn); return (FAILURE); } /* * RDN can be composed of multiple name=value pairs * concatenated by '+'. Hence, we need to determine each * pair and add it to 'rv' */ for (value = rdn, svalue = NULL; *value != '\0'; value++) { if (*value == '+') { /* Make sure it's not escaped */ if (value == rdn || *(value - 1) != '\\') { /* * We are at the start of the new * pair. 'svalue' now contains the * value for the previous pair. Add * the previous pair to 'rv' */ *value = '\0'; if (svalue && addSAttr2RuleValue(rdn, svalue, rv) == -1) { sfree(rdn); freeRuleValue(rv, 1); return (FAILURE); } svalue = NULL; rdn = value + 1; continue; } } if (*value == '=') { if (value == rdn || *(value - 1) != '\\') { /* * 'rdn' now contains the name. * Whatever follows till the next * unescaped '+' or '\0' is the * value for this pair. */ *value = '\0'; svalue = value + 1; continue; } } } /* * End of String. Add the previous name=value pair to 'rv' */ if (svalue && addSAttr2RuleValue(rdn, svalue, rv) == -1) { sfree(rdn); freeRuleValue(rv, 1); return (FAILURE); } sfree(rdn); } else /* rdn == NULL */ return (FAILURE); /* Create the entry */ if (domain) { if (addSAttr2RuleValue("nisDomain", domain, rv) == -1) { freeRuleValue(rv, 1); return (FAILURE); } attrs = scat(myself, F, "objectclass=nisdomainobject,", objClassAttrs); if (!attrs) { freeRuleValue(rv, 1); return (FAILURE); } rc = ldapAdd(dn, rv, attrs, 0); sfree(attrs); } else { rc = ldapAdd(dn, rv, objClassAttrs, 0); } if (rc == LDAP_SUCCESS) logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: Entry (dn: %s) added to DIT", myself, dn); else if (rc == LDAP_ALREADY_EXISTS) /* Treat this as success */ rc = LDAP_SUCCESS; else logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: ldapAdd error %d (%s) for (dn: %s)", myself, rc, ldap_err2string(rc), dn); freeRuleValue(rv, 1); if (ldap_rc) *ldap_rc = rc; return ((rc == LDAP_SUCCESS)?SUCCESS:FAILURE); }