/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include "db_headers.h" #include "db.h" #include "db_mindex.h" #include "db_pickle.h" #include "nisdb_mt.h" #include "nisdb_ldap.h" #include "ldap_nisdbquery.h" #include "ldap_map.h" #include "ldap_ruleval.h" #include "ldap_scheme.h" #include "ldap_parse.h" #include "nis_hashitem.h" #include "nis_db.h" #include "ldap_glob.h" /* Pass through configuration information to the table */ bool_t db_mindex::configure(char *tablePath) { if (tablePath == NULL) return (FALSE); if (objPath.ptr != 0) free(objPath.ptr); objPath.ptr = strdup(tablePath); if (table != NULL) { return (table->configure(tablePath)); } else { /* Defer table config until we have a table instance */ return (objPath.ptr != NULL); } } /* * The noWriteThrough flag is used to prevent modifies/updates to LDAP * while we're incorporating log data into the in-memory tables. */ void db_mindex::setNoWriteThrough(void) { ASSERTWHELD(this->mindex); noWriteThrough.flag++; } void db_mindex::clearNoWriteThrough(void) { ASSERTWHELD(this->mindex); if (noWriteThrough.flag > 0) noWriteThrough.flag--; #ifdef NISDB_LDAP_DEBUG else abort(); #endif /* NISDB_LDAP_DEBUG */ } /* * The noLDAPquery flag is used to prevent recursive LDAP queries when * satisfy_query() is re-entered as we add an entry from queryLDAP(). */ void db_mindex::setNoLDAPquery(void) { ASSERTWHELD(this->mindex); noLDAPquery.flag++; } void db_mindex::clearNoLDAPquery(void) { ASSERTWHELD(this->mindex); if (noLDAPquery.flag > 0) noLDAPquery.flag--; #ifdef NISDB_LDAP_DEBUG else abort(); #endif /* NISDB_LDAP_DEBUG */ } /* * The initialLoad flag tells us if an add or remove is done as part of * the initial load of data, in which case we should use the initial TTLs. */ void db_mindex::setInitialLoad(void) { ASSERTWHELD(this->mindex); initialLoad.flag++; } void db_mindex::clearInitialLoad(void) { ASSERTWHELD(this->mindex); if (initialLoad.flag > 0) initialLoad.flag--; #ifdef NISDB_LDAP_DEBUG else abort(); #endif /* NISDB_LDAP_DEBUG */ } void db_mindex::setDbPtr(void *ptr) { dbptr.ptr = ptr; } void * db_mindex::getDbPtr(void) { return (dbptr.ptr); } db_table * db_mindex::getTable(void) { return (table); } static void setOid(nis_object *obj); extern void db_free_result(db_result *); zotypes updateMappingObj(__nis_table_mapping_t *t, char **objNameP, bool_t *isMasterP) { zotypes type = NIS_BOGUS_OBJ; char *objName = 0; const char *myself = "updateMappingObj"; if (t != 0) objName = t->objName; else if (objNameP != 0) objName = *objNameP; else return (NIS_BOGUS_OBJ); if (objName != 0) { db_status stat; int lstat = LDAP_SUCCESS; nis_object *o = dbFindObject(objName, &stat); /* If not found in the local DB, try LDAP */ if (o == 0) { if (stat != DB_NOTFOUND) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: DB err %d for \"%s\"", myself, stat, NIL(objName)); } o = ldapFindObj(t, objName, &lstat); /* If found, refresh/create the local copy */ if (o != 0) { db_status rstat; rstat = dbRefreshObj(objName, o); if (rstat != DB_SUCCESS) logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: DB error %d refreshing \"%s\"", myself, rstat, NIL(objName)); } } if (o != 0) { type = o->zo_data.zo_type; if (objNameP != 0) { *objNameP = sdup(myself, T, objName); if (*objNameP == 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Unable to copy object name (\"%s\")", myself, NIL(objName)); } } if (t != 0) { if (!setMappingObjTypeEtc(t, o)) nis_destroy_object(o); } else { nis_destroy_object(o); } } else if (lstat != LDAP_SUCCESS) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: LDAP err %d for \"%s\"", myself, lstat, NIL(objName)); } } return (type); } static __nis_table_mapping_t * mappingFromObj(nis_object *obj, int *statP) { __nis_table_mapping_t *t; __nis_buffer_t b = {0, 0}; char *objPath; const char *myself = "mappingFromObj"; if (obj == 0 || obj->zo_data.zo_type == NIS_ENTRY_OBJ) return (0); /* * Convert full object name to the db table path used as * key for the mapping hash list. */ bp2buf(myself, &b, "%s.%s", NIL(obj->zo_name), NIL(obj->zo_domain)); objPath = internalTableName(b.buf); sfree(b.buf); if (slen(objPath) <= 0) { if (statP != 0) *statP = LDAP_OPERATIONS_ERROR; sfree(objPath); return (0); } t = (__nis_table_mapping_t *)__nis_find_item_mt(objPath, &ldapMappingList, 0, 0); sfree(objPath); return (t); } static __nis_table_mapping_t * selectMapping(db_table *table, nis_object *obj, db_query *qin, bool_t wantWrite, bool_t *asObjP, int *statP) { __nis_table_mapping_t *t; __nis_buffer_t b = {0, 0}; bool_t doLDAP, asObj; int stat = LDAP_SUCCESS; char *objPath = 0, buf[MAXPATHLEN+NIS_MAXNAMELEN+1]; const char *myself = "db_mindex::selectMapping"; /* * If 'table' is NULL, we try to find a mapping for 'obj'. * We expect this to happen when our caller wants to write * the object from a directory entry to LDAP. */ if (table == 0) { if (asObjP != 0) *asObjP = TRUE; if (statP != 0) *statP = LDAP_SUCCESS; t = mappingFromObj(obj, statP); if (t == 0) return (0); /* * Should the object type in the mapping be NIS_BOGUS_OBJ, * we need to determine what kind of object this is. */ if (t->objType == NIS_BOGUS_OBJ) { t->objType = updateMappingObj(t, 0, 0); if (t->objType == NIS_BOGUS_OBJ) { if (statP != 0) *statP = LDAP_OPERATIONS_ERROR; return (0); } } /* * If caller wants a mapping suitable for writing, * check that we're the master for this object. */ return (t); } /* * If the object type for the mapping is NIS_BOGUS_OBJ, then * we haven't yet been able to determine what kind of object this * is. Try to fix that now. */ if (table->mapping.objType == NIS_BOGUS_OBJ) { table->mapping.objType = updateMappingObj(table->mapping.tm, &table->mapping.objName, &table->mapping.isMaster); table->mapping.expireType = table->mapping.objType; } /* * Depending on the object type (table->mapping.objType): * * table Use table->mapping.tm to query LDAP * for entries per 'qin'. * * directory Use 'qin' and table->mapping.objName * to retrieve a mapping entry, and then * query LDAP for the corresponding object. * 'qin' == NULL means reading/writing the * entire directory object, plus the names * of the directory entries. * * bogus Not mapping this object. However, we may * still be mapping the object 'obj'. * * other Shouldn't happen; illegal. */ switch (table->mapping.objType) { case NIS_TABLE_OBJ: t = table->mapping.tm; if (wantWrite) doLDAP = table->mapping.isMaster && table->mapping.toLDAP; else doLDAP = table->mapping.fromLDAP; asObj = FALSE; break; case NIS_DIRECTORY_OBJ: { char *sub = 0; int nqc, len = 0; db_qcomp *qc; t = 0; doLDAP = FALSE; asObj = TRUE; /* * We expect the query to have one component, containing * the directory entry name. If there's no query, we want * an enumeration of the entries in the directory. They're * stored with the XDR:ed directory object in LDAP, so * asObj should be TRUE. */ if (qin == 0) { t = table->mapping.tm; if (wantWrite) doLDAP = table->mapping.isMaster && table->mapping.toLDAP; else doLDAP = table->mapping.fromLDAP; asObj = TRUE; break; } nqc = qin->size(); if (nqc != 1 || (qc = qin->queryloc()) == 0 || qc[0].index_value == 0) { stat = LDAP_PARAM_ERROR; break; } qc[0].index_value->get_value(&sub, &len); if (sub == 0 || len <= 0) { stat = LDAP_PARAM_ERROR; break; } /* Append directory name to dir entry name */ sbc2buf(myself, sub, len, &b); bp2buf(myself, &b, ".%s", table->mapping.objName); /* Convert to the DB internal name */ objPath = internal_table_name(b.buf, buf); sfree(b.buf); if (slen(objPath) <= 0) { stat = LDAP_OPERATIONS_ERROR; break; } /* Look for the corresponding table mapping */ t = (__nis_table_mapping_t *)__nis_find_item_mt( objPath, &ldapMappingList, 0, 0); if (t == 0) break; /* Update object mapping information */ if (t->objType == NIS_BOGUS_OBJ) (void) updateMappingObj(t, 0, 0); /* * Should check the objectDN's in 't', but leave that to * underlying functions. */ if (wantWrite) doLDAP = t->isMaster; else doLDAP = TRUE; break; } case NIS_BOGUS_OBJ: t = mappingFromObj(obj, statP); doLDAP = TRUE; asObj = TRUE; break; default: t = 0; doLDAP = FALSE; asObj = TRUE; break; } if (!doLDAP) t = 0; if (asObjP != 0) *asObjP = asObj; if (statP != 0) *statP = stat; return (t); } /* * Replace or remove the table entry identified by 'e'. 'tableName' is * the name of the table (which could be a directory) in which the entry * resides. 'obj' is an un-XDR:ed copy of the object in 'e', optionally * supplied to save re-doing unpacking of the entry object. 'tobj' is * a pointer to the table object; needed for table entries, but not * for directory entries. * * 'ttime' contains the current time, to be supplied for the trans log * entry. * * Returns LDAP_SUCCESS when entry successfully added/modified/deleted, * LDAP_COMPARE_TRUE if an entry to be added/modified was the same as * an already existing one, and a suitable error otherwise. */ int db_mindex::updateTableEntry(entry_object *e, int replace, char *tableName, nis_object *obj, nis_object *tobj, uint32_t ttime, int *xid) { int stat, freeObj = 0; db_index_entry *dbie; long count = 0; bool_t valid = TRUE; db_result *dbres; db_query *qi; nis_object *oldObj = 0; const char *myself = "db_mindex::updateTableEntry"; if (table == 0 || e == 0) return (LDAP_PARAM_ERROR); qi = extract_index_values_from_object(e); if (qi == 0) { logmsg(MSG_NOMEM, LOG_ERR, "%s: Out of memory for query index", myself); return (LDAP_NO_MEMORY); } dbie = satisfy_query(qi, &count, &valid, FALSE); if (dbie != 0 && (count != 1 || !valid)) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: count=%d, valid=%s", myself, count, valid ? "TRUE" : "FALSE"); delete qi; return (LDAP_OPERATIONS_ERROR); } /* * Need a copy of the old object in order to log a removal * (this is true even if we're modifying an existing entry). */ if (dbie != 0) { oldObj = unmakePseudoEntryObj( table->get_entry(dbie->getlocation()), tobj); if (oldObj == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error getting object from old pseudo-entry for \"%s\" in \"%s\"", myself, NIL(obj->zo_name), NIL(tableName)); delete qi; return (LDAP_OPERATIONS_ERROR); } } if (replace) { /* Need the object from the entry */ if (dbie != 0 && obj == 0) { obj = unmakePseudoEntryObj(e, tobj); if (obj == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error getting object from pseudo-entry for \"%s\" in \"%s\"", myself, NIL(obj->zo_name), NIL(tableName)); delete qi; nis_destroy_object(oldObj); return (LDAP_OPERATIONS_ERROR); } freeObj = 1; } /* Is the new object a dup of the old ? */ if (dbie != 0 && sameNisPlusObj(oldObj, obj)) { /* Yes, it's a dup, so just update the timestamp */ table->touchEntry(dbie->getlocation()); delete qi; if (freeObj) nis_destroy_object(obj); nis_destroy_object(oldObj); return (LDAP_COMPARE_TRUE); } else { /* * Not a dup, so go ahead and add it. Provided * that 'qi' isn't NULL (which we've already * checked), DB_ADD(_NOSYNC) does the right * thing even if matching entries already * exist. */ dbres = ((db *)dbptr.ptr)->log_action(DB_ADD_NOSYNC, qi, e); if (dbres == 0) stat = LDAP_OPERATIONS_ERROR; else if (dbres->status == DB_SUCCESS) stat = LDAP_SUCCESS; else stat = LDAP_OPERATIONS_ERROR; db_free_result(dbres); } } else { /* Removing */ /* If the object doesn't exist, we're done */ if (dbie == 0) { delete qi; return (LDAP_SUCCESS); } dbres = ((db *)dbptr.ptr)->log_action(DB_REMOVE_NOSYNC, qi, 0); if (dbres == 0) stat = LDAP_OPERATIONS_ERROR; else if (dbres->status == DB_SUCCESS) stat = LDAP_SUCCESS; else stat = LDAP_OPERATIONS_ERROR; db_free_result(dbres); } /* Log the operation */ if (stat == LDAP_SUCCESS) { int ret, numAttrs; nis_attr *attr, attrbuf[NIS_MAXCOLUMNS]; /* If we haven't begun the transaction yet, do so now */ if (*xid == 0) { *xid = beginTransaction(); if (*xid == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error starting transaction for \"%s\"", myself, NIL(tableName)); delete qi; if (oldObj != 0) nis_destroy_object(oldObj); return (LDAP_OPERATIONS_ERROR); } } if (replace && obj == 0) { obj = unmakePseudoEntryObj(e, tobj); if (obj == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error getting object from pseudo-entry for \"%s\" in \"%s\"", myself, NIL(obj->zo_name), NIL(tableName)); delete qi; if (oldObj != 0) nis_destroy_object(oldObj); return (LDAP_OPERATIONS_ERROR); } freeObj = 1; } /* * The log stores nis_attr information, so we need to * convert the scheme-query to a nis_attr array. */ attr = schemeQuery2nisAttr(qi, attrbuf, scheme, table->mapping.tm, &numAttrs); if (attr == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error converting index query to nis_attr for \"%s\" in \"%s\"", myself, NIL(obj->zo_name), NIL(tableName)); if (freeObj) nis_destroy_object(obj); if (oldObj != 0) nis_destroy_object(oldObj); delete qi; return (LDAP_OPERATIONS_ERROR); } if (replace) { /* * While the DB can handle a modify (replace) * operation, the trans log stores this as a * remove followed by an add (which allows * backing out the change by removing the new * object incarnation, and adding the old one). */ if (oldObj != 0) ret = addUpdate(REM_IBASE, tableName, numAttrs, attr, oldObj, 0, ttime); else ret = 0; if (ret == 0) ret = addUpdate(ADD_IBASE, tableName, numAttrs, attr, obj, 0, ttime); } else { /* Removal */ ret = addUpdate(REM_IBASE, tableName, numAttrs, attr, oldObj, 0, ttime); } if (ret != 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error adding trans log entry for \"%s\" in \"%s\"", myself, NIL(obj->zo_name), NIL(tableName)); stat = LDAP_OPERATIONS_ERROR; } } delete qi; if (oldObj != 0) nis_destroy_object(oldObj); if (freeObj) nis_destroy_object(obj); return (stat); } bool_t db_mindex::touchEntry(entry_object *e) { db_query *qi; bool_t ret; if (table == 0 || e == 0) return (FALSE); qi = extract_index_values_from_object(e); if (qi == 0) return (FALSE); ret = touchEntry(qi); delete qi; return (ret); } bool_t db_mindex::touchEntry(db_query *q) { db_index_entry *dbie; long count; bool_t valid; dbie = satisfy_query(q, &count, &valid, FALSE); if (dbie != 0 && count == 1 && valid) table->touchEntry(dbie->getlocation()); else return (FALSE); return (TRUE); } /* * Compose an object name from column zero of 'e' and 't->objName', * and return the mapping for that object, if any. Also set '*name' * to point to the dir entry name in 'e'. Note that this is a pointer * to existing data, and shouldn't be freed other than as part of * freeing 'e'. */ static __nis_table_mapping_t * findDirEntryMapping(__nis_table_mapping_t *t, entry_object *e, char **name) { __nis_table_mapping_t *x; char *entryName; const char *myself = "findDirEntryMapping"; __nis_buffer_t b = {0, 0}; if (e == 0 || e->en_cols.en_cols_len != 2 || e->en_cols.en_cols_val == 0) return (0); entryName = e->en_cols.en_cols_val[1].ec_value.ec_value_val; if (name != 0) *name = entryName; if (t == 0 || entryName == 0 || t->objName == 0) return (0); bp2buf(myself, &b, "%s.%s", entryName, t->objName); if (b.len == 0 || b.buf == 0) return (0); x = (__nis_table_mapping_t *)__nis_find_item_mt(b.buf, &ldapMappingList, 0, 0); sfree(b.buf); return (x); } /* * Query LDAP per the supplied (scheme-) query 'qin'. If 'doAsynch' is * set, and the query is an enumeration (qin == 0), the query will be * performed in a detached thread, and complete asynchronously. In this * case, the return status reflects the setup and launch of the * detached thread; the query will complete asynchronously. * * Returns an appropriate LDAP status code. */ int db_mindex::queryLDAP(db_query *qin, char *dbId, int doAsynch) { __nis_table_mapping_t *t; int i, na, nq = 0, stat, stat2, numAttrs, ret; int xid = 0; long numEa; bool_t asObj, doEnum; db_query *q; entry_object **ea; nis_attr attr; nis_object *dirObj; db_status dstat; const char *myself = "db_mindex::queryLDAP"; if (!useLDAPrespository || table == 0) return (LDAP_SUCCESS); /* * Instances from the deferred dictionary shouldn't change, * there's no point in querying LDAP. */ if (table->mapping.isDeferredTable) return (LDAP_SUCCESS); t = selectMapping(table, 0, qin, FALSE, &asObj, &stat); if (t == 0) return (stat); #ifdef NISDB_LDAP_DEBUG printf("%s: %s (%s)\n", myself, NIL(t->objName), (asObj ? "object" : "entry")); #endif /* NISDB_LDAP_DEBUG */ if (qin != NULL) { q = schemeQuery2Query(qin, scheme); if (q == 0) return (LDAP_PARAM_ERROR); #ifdef NISDB_LDAP_DEBUG q->print(); #endif /* NISDB_LDAP_DEBUG */ } else { q = 0; #ifdef NISDB_LDAP_DEBUG printf("\tenumerating %s%s%s\n", dbId ? dbId : "", dbId ? ":" : "", NIL(t->objName)); #endif /* NISDB_LDAP_DEBUG */ } /* * Do we have any active mappings for this particular query and * dbId ? If not, we're done. * * Note that we don't care about the return value from * selectTableMapping(), just wheter or not there are * any valid mappings. */ i = 0; sfree(selectTableMapping(t, q, 0, asObj, dbId, &i)); if (i <= 0) { freeQuery(q); return (LDAP_SUCCESS); } /* Is the object a directory ? */ if (asObj) { nis_object *o; entry_object *e, eo; entry_col ec[2]; int nea; stat = objFromLDAP(t, &o, &ea, &nea); numEa = nea; if (stat == LDAP_NO_SUCH_OBJECT) { /* Positive failure; remove the object */ dstat = dbDeleteObj(t->objName); if (dstat == DB_SUCCESS || dstat == DB_NOTFOUND) { stat = LDAP_SUCCESS; } else { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: DB error %d deleting \"%s\"", myself, dstat, NIL(t->objName)); stat = LDAP_OPERATIONS_ERROR; } freeQuery(q); return (stat); } else if (stat != LDAP_SUCCESS) { freeQuery(q); return (stat); } else if (o == 0) { /* OK; this entry just isn't mapped */ freeQuery(q); return (LDAP_SUCCESS); } if (q != 0) { /* * We're updating one particular entry (described * by 't') in the directory 'table->mapping.tm'. */ setOid(o); dstat = dbRefreshObj(t->objName, o); if (dstat == DB_SUCCESS) { stat = LDAP_SUCCESS; } else { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: DB error %d updating \"%s\" in \"%s\"", myself, NIL(t->objName), NIL(table->mapping.tm->objName)); stat = LDAP_OPERATIONS_ERROR; } freeEntryObjArray(ea, numEa); freeQuery(q); nis_destroy_object(o); return (stat); } dirObj = o; /* * q == 0, so we're enumerating. Update the list of * directory entries. */ /* * Need to disable write-through to LDAP, for which we need * a lock on our db_mindex ('this'); we're also updating the * table, so we need a write lock on that as well. */ WRITELOCKNR(this, stat, "w db_mindex::queryLDAP"); if (stat == 0) { WRITELOCKNR(table, stat2, "table w db_mindex::queryLDAP"); } if (stat != 0 || stat2 != 0) { nis_destroy_object(dirObj); logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: lock error %d", myself, stat != 0 ? stat : stat2); return (LDAP_OPERATIONS_ERROR); } setNoWriteThrough(); setNoLDAPquery(); table->setEnumMode(0); for (i = 0, na = 0; i < numEa; i++) { int st; __nis_table_mapping_t *x; char *name = 0; entry_obj *e; if (ea[i] == 0) continue; /* * We've got a list of dir entries. In the general, * case, some are new, and some already exist. * We definitely want to add the new ones, and to * that end, we need a copy of the object for the * entry. By definition, if an entry is new, we * don't yet have a copy of the object for it, so * it's LDAP or nothing. * * If the entry already exists, try to update the * entry object. In this case, we again only need * to look in LDAP for the object; if there already * is one in the DB, it's in the dir entry which we * want to update. * * So, whether adding or replacing, try to get the * object from LDAP. * * If we can't get a copy of the object, there's not * much point in adding or updating (since a dir * entry just consists of the entry object and name), * so we continue to the next entry. * * However, in that case, we do need to touch the * dir entry; otherwise, it will be removed later * on. */ x = findDirEntryMapping(t, ea[i], &name); o = 0; if (x == 0 || (st = objFromLDAP(x, &o, 0, 0)) != LDAP_SUCCESS) { if (x != 0) logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Unable to obtain object for \"%s\" in \"%s\": %s", myself, NIL(name), NIL(t->objName), ldap_err2string(st)); if (o != 0) nis_destroy_object(o); if (!touchEntry(ea[i])) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Inconsistency: LDAP-derived directory \"%s\" " "contains entry \"%s\", which is unknown locally, " "and has no LDAP mapping", myself, NIL(t->objName), NIL(name)); } continue; } if (ea[i]->en_cols.en_cols_len != 2 || ea[i]->en_cols.en_cols_val == 0 || ea[i]->en_cols.en_cols_val[0]. ec_value.ec_value_val != 0 || ea[i]->en_cols.en_cols_val[0]. ec_value.ec_value_len != 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Illegal entry_obj col 0 for \"%s\" in \"%s\"", myself, NIL(name), NIL(t->objName)); nis_destroy_object(o); touchEntry(ea[i]); continue; } setOid(o); e = makePseudoEntryObj(o, ea[i], 0); if (e == 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Unable to create pseudo entry object for \"%s\" in \"%s\"", myself, NIL(name), NIL(t->objName)); nis_destroy_object(o); touchEntry(ea[i]); continue; } st = updateTableEntry(e, 1, t->objName, o, 0, o->zo_oid.mtime, &xid); if (st == LDAP_SUCCESS) { na++; } else if (st == LDAP_COMPARE_TRUE) { /* OK, same as existing entry */ st = LDAP_SUCCESS; } else { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Error updating directory entry for \"%s\" in \"%s\": %s", myself, NIL(name), NIL(t->objName), ldap_err2string(st)); if (stat == LDAP_SUCCESS) stat = st; } /* Free the XDR buffer */ sfree(e->en_cols.en_cols_val[0]. ec_value.ec_value_val); /* Restore ea[i] */ ea[i]->en_cols.en_cols_val[0]. ec_value.ec_value_val = 0; ea[i]->en_cols.en_cols_val[0]. ec_value.ec_value_len = 0; nis_destroy_object(o); } freeEntryObjArray(ea, numEa); /* Get list of entries to remove */ ea = table->endEnumMode(&numEa); if (ea != 0) { uint32_t nowt = time(0); for (i = 0; i < numEa; i++) { int st; if (ea[i] == 0) continue; st = updateTableEntry(ea[i], 0, t->objName, 0, 0, nowt, &xid); if (st == LDAP_SUCCESS) { na++; } else { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Error removing directory entry for \"%s\": %s", myself, NIL(t->objName), ldap_err2string(st)); if (stat == LDAP_SUCCESS) stat = st; } } } if (stat == LDAP_SUCCESS) { struct timeval now; (void) gettimeofday(&now, 0); table->mapping.enumExpire = now.tv_sec + table->mapping.ttl; } if (na > 0) (void) ((db *)dbptr.ptr)->sync_log(); if (xid != 0 && na > 0 && stat == LDAP_SUCCESS) { ret = endTransaction(xid, dirObj); if (ret != 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error ending transaction for \"%s\"", myself, NIL(t->objName)); stat = LDAP_OPERATIONS_ERROR; } } else if (xid != 0) { ret = abort_transaction(xid); if (ret != 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error aborting transaction for \"%s\"", myself, NIL(t->objName)); } } nis_destroy_object(dirObj); sfree(ea); clearNoLDAPquery(); clearNoWriteThrough(); WRITEUNLOCK2(table, this, stat, stat, "table wu db_mindex::queryLDAP", "wu db_mindex::queryLDAP"); return (stat); } /* * In order to ping replicas, if any, we need to find the * directory containing the table to be updated. If we * can't find the directory object, we're sunk, so let's * start with that. */ if (t->isMaster) { dirObj = findObj(t->obj->zo_domain, &dstat, &stat); if (dirObj == 0) { if (stat == LDAP_SUCCESS) stat = LDAP_OPERATIONS_ERROR; return (stat); } } else { dirObj = 0; } stat = entriesFromLDAP(t, qin, q, dbId, dirObj, doAsynch); return (stat); } extern db *tableDB(char *); /* * Remove the LDAP entry/entries corresponding to 'qin'/'obj'. */ int db_mindex::removeLDAP(db_query *qin, nis_object *obj) { __nis_table_mapping_t *t; db_query *q; bool_t asObj; int stat; if (!useLDAPrespository || table == 0) return (LDAP_SUCCESS); /* Instances from the deferred dictionary should not update LDAP */ if (table->mapping.isDeferredTable) return (LDAP_SUCCESS); t = selectMapping(table, 0, qin, TRUE, &asObj, &stat); if (t == 0 && stat != LDAP_SUCCESS) return (stat); #ifdef NISDB_LDAP_DEBUG if (t != 0) printf("removeLDAP: %s\n", NIL(t->objName)); #endif /* NISDB_LDAP_DEBUG */ if (qin != NULL) { if (asObj) { /* * selectMapping() gave us the mapping for the * directory entry. However, if 't' is NULL, this * could be due to the directory itself not being * mapped, in which case we must obtain the mapping * info from 'obj'. */ if (t == 0) { t = selectMapping(0, obj, 0, TRUE, &asObj, &stat); if (t == 0 && stat != LDAP_SUCCESS) return (stat); } if (t != 0) { stat = deleteLDAPobj(t); /* * If we were successful, update the object * stored with the mapping. */ if (stat == LDAP_SUCCESS) (void) replaceMappingObj(t, 0); else return (stat); } /* * Since it's a directory entry we've removed, we also * need to update the directory object itself. */ stat = storeLDAP(0, 0, 0, 0, 0); } else { q = schemeQuery2Query(qin, scheme); if (q == 0) return (LDAP_PARAM_ERROR); #ifdef NISDB_LDAP_DEBUG q->print(); #endif /* NISDB_LDAP_DEBUG */ stat = mapToLDAP(t, 1, &q, 0, 0, 0, 0); freeQuery(q); } } else { /* * This isn't the way to remove the LDAP entries * corresponding to the entire table. */ #ifdef NISDB_LDAP_DEBUG abort(); #endif /* NISDB_LDAP_DEBUG */ stat = LDAP_PARAM_ERROR; } return (stat); } /* * Helper function for storeLDAP() which handles updates for objects * other than table entries. */ int db_mindex::storeObjLDAP(__nis_table_mapping_t *t, nis_object *o) { int stat, assigned = 0; entry_object **ea; int numEa, doUnlock = 0; db *dbase = 0; db_mindex *dbm = 0; const char *myself = "db_mindex::storeObjLDAP"; if (t == 0 || o == 0) return (LDAP_SUCCESS); /* * If the object to be stored is anything other than a * directory, we can just go ahead and write it to LDAP. * A directory object, however, also needs a directory * entry list, so we should to get hold of the db_table * that goes with the directory. */ if (o->zo_data.zo_type == NIS_DIRECTORY_OBJ) { dbase = tableDB(t->objName); if (dbase != 0) dbm = dbase->mindex(); if (dbase == 0 || dbm == 0 || dbm->table == 0) { /* By definition, no dir entries */ ea = 0; numEa = 0; dbase = 0; } else { entry_object **tea; long i, ntea; /* * Read-lock the table so that 'tab' * doesn't change while we're using it. */ READLOCK(dbm->table, LDAP_OPERATIONS_ERROR, "r table db_mindex::storeLDAP"); doUnlock = 1; tea = dbm->table->gettab(); ntea = dbm->table->getsize(); /* * There may be empty slots in the table 'tab' * array, so get rid of those. */ if (tea != 0 && ntea > 0) { ea = (entry_object **)am(myself, ntea * sizeof (ea[0])); if (ea == 0) { READUNLOCK(dbm->table, LDAP_NO_MEMORY, "ru table db_mindex::storeLDAP"); return (LDAP_NO_MEMORY); } for (i = 0, numEa = 0; i < ntea; i++) { if (tea[i] != 0) { ea[numEa] = tea[i]; numEa++; } } if (numEa == 0) { /* No non-empty slots */ sfree(ea); ea = 0; READUNLOCK(dbm->table, LDAP_OPERATIONS_ERROR, "ru table db_mindex::storeLDAP"); doUnlock = 0; } } else { ea = 0; numEa = 0; READUNLOCK(dbm->table, LDAP_OPERATIONS_ERROR, "ru table db_mindex::storeLDAP"); doUnlock = 0; } } } else { ea = 0; numEa = 0; } stat = objToLDAP(t, o, ea, numEa); if (ea != 0) sfree(ea); if (doUnlock) { READUNLOCK(dbm->table, stat, "ru table db_mindex::storeLDAP"); } return (stat); } /* * Store data specified by the index-query 'qin' to LDAP. If 'obj' is * non-null, it's a pointer to the pseudo-entry object corresponding to * 'qin'. As a short-cut/convenience, the caller can instead supply * the actual nis_object 'o'; if 'o' is NULL, it's derived from 'obj'. * * 'oldObj' is used for table entries if the store operation is * an update, and the corresponding NIS+ operation was a delete followed * by an add. In this case, oldObj contains the pre-delete incarnation of * the entry object to be modified. * * The 'dbId' string is used to select one dbId for mapping chains * that contain more than one. * * Returns an LDAP status code. */ int db_mindex::storeLDAP(db_query *qin, entry_object *obj, nis_object *o, entry_obj *oldObj, char *dbId) { __nis_table_mapping_t *t; bool_t asObj; db_query *q, *qo, **qa; __nis_rule_value_t *rv = 0; int stat; const char *myself = "db_mindex::storeLDAP"; if (!useLDAPrespository || table == 0) return (LDAP_SUCCESS); /* Instances from the deferred dictionary should not update LDAP */ if (table->mapping.isDeferredTable) return (LDAP_SUCCESS); t = selectMapping(table, 0, qin, TRUE, &asObj, &stat); if (t == 0 && stat != LDAP_SUCCESS) return (stat); #ifdef NISDB_LDAP_DEBUG if (t != 0) printf("storeLDAP: %s%s%s\n", dbId ? dbId : "", dbId ? ":" : "", NIL(t->objName)); #endif /* NISDB_LDAP_DEBUG */ /* * selectMapping() didn't have the object to look at, so we * must check if this is a directory entry or not. */ if (asObj) { if (o != 0) { if (o->zo_data.zo_type == NIS_ENTRY_OBJ) asObj = FALSE; } else if (obj != 0) { if (obj->en_type == 0 || strcmp(obj->en_type, "IN_DIRECTORY") != 0) asObj = FALSE; } } if (asObj) { bool_t freeO = FALSE; /* * If we don't have a mapping, that's probably because * the directory (represented by 'this') isn't mapped. * Try to get a mapping from 'o' or 'obj'. */ if (t == 0) { if (o == 0 && obj != 0) { o = unmakePseudoEntryObj(obj, 0); if (o == 0) return (LDAP_OPERATIONS_ERROR); freeO = TRUE; } if (o != 0) { t = selectMapping(0, o, 0, TRUE, &asObj, &stat); if (t == 0) { if (freeO) nis_destroy_object(o); return (stat); } } } /* * If we found a mapping for the 'table' in this db_mindex, * store the object. */ if (t != 0) { if (o == 0) { if (obj != 0) { o = unmakePseudoEntryObj(obj, 0); freeO = TRUE; } else { db_status dstat; o = dbFindObject(t->objName, &dstat); if (o == 0) logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: DB error %d finding \"%s\"", myself, NIL(t->objName)); freeO = TRUE; } } if (o == 0) return (LDAP_OPERATIONS_ERROR); stat = storeObjLDAP(t, o); /* * Store the object with the mapping. If 'o' was * supplied by the caller, we first need to make * a copy. */ if (!freeO) { o = nis_clone_object(o, 0); if (o == 0) logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Unable to refresh mapping object for \"%s\"", myself, NIL(t->objName)); } if (o != 0) { if (!replaceMappingObj(t, o)) nis_destroy_object(o); } /* * Object now either destroyed or stored in 't'. * Set pointer to NULL in order to avoid freeing * it below. */ o = 0; if (stat != LDAP_SUCCESS) return (stat); } if (freeO && o != 0) { nis_destroy_object(o); o = 0; } /* * If the entry object 'obj' has the type "IN_DIRECTORY", * then it's a directory entry, and we should check if * the directory is mapped to LDAP, and update the dir * entry list accordingly. */ if (obj == 0 || obj->en_type == 0 || strcmp(obj->en_type, "IN_DIRECTORY") != 0) return (LDAP_SUCCESS); /* Does it have a mapping ? */ t = selectMapping(table, 0, 0, TRUE, &asObj, &stat); if (t == 0) return (stat); stat = storeObjLDAP(t, t->obj); return (stat); } /* Store table entries. If we don't have a mapping, we're done. */ if (t == 0) return (LDAP_SUCCESS); if (qin != NULL && obj != NULL) { db_index_entry *dbie; int i, size, nq = 0; long l, count; bool_t valid; db_query qbuf, **qold; rv = (__nis_rule_value_t *)am(myself, sizeof (*rv)); qa = (db_query **)am(myself, sizeof (qa[0])); if (oldObj != 0) { /* * Note that only qold[0] is a unique query pointer. * All the other qold[i]'s are copies of qa[i]. * Hence, we only free qold[0], as well as qold * itself. */ qold = (db_query **)am(myself, sizeof (qold[0])); } else { qold = 0; } if (rv == 0 || qa == 0 || (oldObj != 0 && qold == 0)) { sfree(rv); sfree(qa); sfree(qold); return (LDAP_NO_MEMORY); } q = schemeQuery2Query(qin, scheme); if (q == 0) { sfree(rv); sfree(qa); return (LDAP_PARAM_ERROR); } qa[0] = pseudoEntryObj2Query(obj, t->obj, &rv[0]); if (qa[0] == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Unable to obtain query representation of new entry object for \"%s\"", myself, NIL(t->dbId)); freeQuery(q); sfree(rv); sfree(qa); sfree(qold); return (LDAP_OPERATIONS_ERROR); } if (oldObj != 0) { qold[0] = pseudoEntryObj2Query(oldObj, t->obj, 0); if (qold[0] == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Unable to obtain query representation of old entry object for \"%s\"", myself, NIL(t->dbId)); freeQueries(qa, 1); freeQuery(q); sfree(rv); sfree(qa); sfree(qold); return (LDAP_OPERATIONS_ERROR); } } nq++; /* * In order to support many-to-one NIS+ to LDAP mapping, * we need to find all possible matches in the NIS+ DB, * and then merge to produce a single update. mapToLDAP() * takes care of the merging, so our job is to collect * the matches. Worst case is that we need to search * individually for each component in 'qin', so that's * what we'll do. * * mapToLDAP() has a mode that only performs an update * for the first DN, and that's what we want. In order * to make sure that it's the correct DN, we leave the * original query as the first one passed to mapToLDAP(). */ size = qin->size(); /* For each component of 'qin' */ for (i = 0; i < size; i++) { db_query *qc, **qat, **qoldt; long j; __nis_rule_value_t *rvt; qc = queryFromComponent(qin, i, &qbuf); if (qc == 0) continue; dbie = satisfy_query_dbonly(qc, &count, FALSE, &valid); if (dbie == 0 || !valid || count <= 0) continue; rvt = (__nis_rule_value_t *)realloc(rv, (nq+count) * sizeof (rv[0])); qat = (db_query **)realloc(qa, (nq+count) * sizeof (qa[0])); if (qold != 0) qoldt = (db_query **)realloc(qold, (nq+count) * sizeof (qold[0])); if (rvt == 0 || qat == 0 || (qold != 0 && qoldt == 0)) { if (qat == 0) freeQueries(qa, nq); else freeQueries(qat, nq); if (rvt == 0) freeRuleValue(rv, nq); else freeRuleValue(rvt, nq); if (qold != 0) { if (qoldt == 0) freeQueries(qold, 1); else freeQueries(qoldt, 1); } freeQuery(q); (void) memset(&qbuf, 0, sizeof (qbuf)); logmsg(MSG_NOMEM, LOG_ERR, "%s: realloc(%d) failed", myself, (nq+count) * sizeof (void *)); return (LDAP_NO_MEMORY); } rv = rvt; qa = qat; (void) memset(&rv[nq], 0, count * sizeof (rv[0])); (void) memset(&qa[nq], 0, count * sizeof (qa[0])); if (qold != 0) { qold = qoldt; (void) memset(&qold[nq], 0, count * sizeof (qold[0])); } for (j = 0; j < count; j++) { qa[nq] = pseudoEntryObj2Query( table->get_entry(dbie->getlocation()), t->obj, &rv[nq]); if (qa[nq] == 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Could not create query from entry obj for \"%s\"", myself, NIL(t->objName)); freeQueries(qa, nq); freeQueries(qold, 1); freeRuleValue(rv, nq); freeQuery(q); (void) memset(&qbuf, 0, sizeof (qbuf)); return (LDAP_PARAM_ERROR); } if (qold != 0) qold[nq] = qa[nq]; nq++; dbie = dbie->getnextresult(); if (dbie == 0) break; } } stat = mapToLDAP(t, nq, (qold != 0 ? qold : qa), qa, rv, 1, dbId); freeQueries(qa, nq); freeRuleValue(rv, nq); freeQuery(q); freeQueries(qold, 1); (void) memset(&qbuf, 0, sizeof (qbuf)); } else if (qin == 0 && obj == 0 && t->objType == NIS_TABLE_OBJ) { long i, j, ntab; entry_object **tab; READLOCK(table, LDAP_OPERATIONS_ERROR, "r table db_mindex::storeLDAP"); tab = table->gettab(); ntab = table->getsize(); if (tab == 0 || ntab <= 0) { READUNLOCK(table, LDAP_OPERATIONS_ERROR, "ru table db_mindex::storeLDAP"); return (LDAP_SUCCESS); } qa = (db_query **)am(myself, ntab * sizeof (qa[0])); rv = (__nis_rule_value_t *)am(myself, ntab * sizeof (rv[0])); if (qa == 0 || rv == 0) { sfree(qa); sfree(rv); READUNLOCK(table, LDAP_OPERATIONS_ERROR, "ru table db_mindex::storeLDAP"); return (LDAP_NO_MEMORY); } for (i = 0; i < ntab; i++) { if (tab[i] == 0) continue; qa[i] = pseudoEntryObj2Query(tab[i], t->obj, &rv[i]); if (qa[i] == 0) { freeQueries(qa, i); freeRuleValue(rv, i); logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Could not create query from entry for \"%s\"", myself, NIL(t->objName)); READUNLOCK(table, LDAP_OPERATIONS_ERROR, "ru table db_mindex::storeLDAP"); return (LDAP_OPERATIONS_ERROR); } } stat = mapToLDAP(t, ntab, qa, qa, rv, 0, dbId); freeQueries(qa, ntab); freeRuleValue(rv, ntab); if (stat == LDAP_SUCCESS) { struct timeval now; int lstat, lck = 1; /* * Since we've just successfully uploaded everthing * in this table, we now consider our local copy * up-to-date as well. */ (void) gettimeofday(&now, 0); WRITELOCKNR(table, lstat, "w table db_mindex::storeLDAP"); if (lstat == 0) { table->mapping.enumExpire = now.tv_sec + table->mapping.ttl; lck = 0; WRITEUNLOCKNR(table, lstat, "wu table db_mindex::storeLDAP"); } if (lstat != 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: %sock error %d for \"%s\"%s", myself, lck?"L":"Unl", lstat, NIL(t->objName), lck ? "; unable to update enumeration expiration": ""); } } READUNLOCK(table, stat, "ru table db_mindex::storeLDAP"); } return (stat); } /* * Sets the oid (i.e., the creation and modification times) for the * specified object. In order to avoid retrieving the old incarnation * (if any) from the DB first, we're punting and setting both mtime * and ctime to the current time. */ static void setOid(nis_object *obj) { if (obj != 0) { obj->zo_oid.ctime = obj->zo_oid.mtime = time(0); } }