/* * 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 */ /* * nis_db.cc * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2015 RackTop Systems. */ #include #include #include #include "nisdb_mt.h" #include "db_headers.h" #include "db_entry.h" #include "db.h" #include "db_dictionary.h" #include "db_pickle.h" #include "nis_db.h" #include "nis_ldap.h" #include "ldap_util.h" #include "ldap_parse.h" #include "ldap_glob.h" #include "ldap_xdr.h" #include "ldap_glob.h" db_dictionary curdict; db_dictionary tempdict; /* a temporary one */ db_dictionary *InUseDictionary = &curdict; db_dictionary *FreeDictionary = &tempdict; extern "C" { static db_result *db_add_entry_x(char *tab, int numattrs, nis_attr *attrname, entry_obj * newobj, int skiplog, int nosync); db_status db_table_exists(char *table_name); /* * (Imported from rpc.nisd/nis_xx_proc.c) * * 'tbl_prototype' is used to create a table that holds a directory. */ static table_col cols[2] = { {(char *)"object", TA_BINARY+TA_XDR, 0}, {(char *)"name", TA_CASE+TA_SEARCHABLE, 0} }; table_obj tbl_prototype = { (char *)"DIRECTORY", 2, ' ', {2, &cols[0]}, NULL }; } /* * Free resources associated with a db_result structure */ void db_free_result(db_result *dr) { int i; if (dr == 0) return; /* Can't have valid objects */ if (dr->status != DB_SUCCESS) { free(dr); return; } for (i = 0; i < dr->objects.objects_len; i++) free_entry(dr->objects.objects_val[i]); free(dr->objects.objects_val); free(dr); } /* Return an empty db_result structure with its status field set to 's'. */ db_result* empty_result(db_status s) { db_result * res = new db_result; if (res != NULL) { res->status = s; res->nextinfo.db_next_desc_len = 0; res->nextinfo.db_next_desc_val = NULL; res->objects.objects_len = 0; res->objects.objects_val = NULL; } else { WARNING("nis_db::empty_result: cannot allocate space"); } return (res); } static db_result* set_result(db_result* res, db_status s) { if (res != NULL) { res->status = s; } return (res); } /* * Given a FQ object name for a table or directory, return the (db *) * corresponding to the object. */ db * tableDB(char *tableName) { db_table_desc *tbl = 0; char *intName; db *dbase; intName = internalTableName(tableName); if (intName == 0) return (0); dbase = InUseDictionary->find_table(intName, &tbl); sfree(intName); return (dbase); } extern "C" { bool_t db_in_dict_file(char *name) { return (InUseDictionary->find_table_desc(name) != NULL); } const char *db_perror(db_status dbstat) { const char *str = NULL; switch (dbstat) { case DB_SUCCESS: str = "Success"; break; case DB_NOTFOUND: str = "Not Found"; break; case DB_BADTABLE: str = "Bad Table"; break; case DB_BADQUERY: str = "Bad Query"; break; case DB_BADOBJECT: str = "Bad Object"; break; case DB_MEMORY_LIMIT: str = "Memory limit exceeded"; break; case DB_STORAGE_LIMIT: str = "Database storage limit exceeded"; break; case DB_INTERNAL_ERROR: str = "Database internal error"; break; case DB_SYNC_FAILED: str = "Sync of log file failed"; break; default: str = "Unknown Error"; break; } return (str); } bool_t db_extract_dict_entries(char *newdict, char **fs, int fscnt) { /* * Use the "FreeDictionary" ptr for the backup * dictionary. */ if (!FreeDictionary->inittemp(newdict, *InUseDictionary)) return (FALSE); return (InUseDictionary->extract_entries (*FreeDictionary, fs, fscnt)); } bool_t db_copy_file(char *infile, char *outfile) { return (InUseDictionary->copyfile(infile, outfile)); } /* * The tok and repl parameters will allow us to merge two dictionaries * that reference tables from different domains (master/replica in live * in different domains). If set to NULL, then the dictionary merge is * done as normal (no name changing). */ db_status db_begin_merge_dict(char *newdict, char *tok, char *repl) { db_status dbstat; /* * It is assumed that InUseDictionary has already been initialized. */ dbstat = InUseDictionary->checkpoint(); if (dbstat != DB_SUCCESS) return (dbstat); /* * Use the "FreeDictionary" ptr for the backup * dictionary. */ if (!FreeDictionary->init(newdict)) return (DB_INTERNAL_ERROR); return (InUseDictionary->merge_dict(*FreeDictionary, tok, repl)); } db_status db_end_merge_dict() { db_status dbstat; dbstat = InUseDictionary->checkpoint(); if (dbstat != DB_SUCCESS) { return (dbstat); } dbstat = InUseDictionary->db_shutdown(); if (dbstat != DB_SUCCESS) { return (dbstat); } dbstat = FreeDictionary->db_shutdown(); if (dbstat != DB_SUCCESS) { return (dbstat); } return (dbstat); } db_status db_abort_merge_dict() { db_status dbstat; dbstat = InUseDictionary->db_shutdown(); if (dbstat != DB_SUCCESS) return (dbstat); dbstat = FreeDictionary->db_shutdown(); return (dbstat); } /* * Initialize system (dictionary) using file 'filename'. If system cannot * be read from file, it is initialized to be empty. Returns TRUE if * initialization succeeds, FALSE otherwise. * This function must be called before any other. */ bool_t db_initialize(char * filename) { return (InUseDictionary->init(filename)); } /* * Massage the dictionary file by replacing the specified token with the * the replacement string. This function is needed to provide backwards * compatibility for providing a transportable dictionary file. The idea * is that rpc.nisd will call this function when it wants to change the * /var/nis/ strings with something like /var/nis/data. * */ db_status db_massage_dict(char *newdictname, char *tok, char *repl) { return (InUseDictionary->massage_dict(newdictname, tok, repl)); } /* * Create new table using given table name and table descriptor. * Returns DB_SUCCESS if successful; appropriate error code otherwise. */ db_status db_create_table(char * table_name, table_obj * table_desc) { return (InUseDictionary->add_table(table_name, table_desc)); } /* * Destroys table named by 'table_name.' Returns DB_SUCCESS if successful, * error code otherwise. Note that currently, the removed table is no * longer accessible from this interface and all files associated with it * are removed from stable storage. */ db_status db_destroy_table(char * table_name) { return (InUseDictionary->delete_table(table_name)); } /* * Return a copy of the first entry in the specified table, that satisfies * the given attributes. The returned structure 'db_result' contains the status, * the copy of the object, and a 'db_next_desc' to be used for the 'next' * operation. */ db_result * db_first_entry(char * table_name, int numattrs, nis_attr * attrname) { db_result * safety = empty_result(DB_SUCCESS); db_table_desc * tbl = NULL; db * dbase = InUseDictionary->find_table(table_name, &tbl); if (tbl == NULL || dbase == NULL) return (set_result(safety, DB_BADTABLE)); else { db_result * res = NULL; db_query *query = NULL; if (numattrs != 0) { query = InUseDictionary->translate_to_query(tbl, numattrs, attrname); if (query == NULL) return (set_result(safety, DB_BADQUERY)); } res = dbase->execute(DB_FIRST, query, NULL, NULL); if (query) delete query; if (safety) delete safety; return (res); } } /* * Return a copy of the next entry in the specified table as specified by * the 'next_desc'. The returned structure 'db_result' contains the status, * a copy of the object, and a db_next_desc to be used for a subsequent * 'next' operation. */ db_result * db_next_entry(char * table_name, db_next_desc * next_desc) { db_result * safety = empty_result(DB_SUCCESS); db * dbase = InUseDictionary->find_table(table_name); if (dbase != NULL) { if (safety) delete safety; return (dbase->execute(DB_NEXT, NULL, NULL, next_desc)); } else return (set_result(safety, DB_BADTABLE)); } /* * Indicate to the system that you are no longer interested in the rest of the * results identified by [next_desc]. After executing this operation, the * [next_desc] is no longer valid (cannot be used as an argument for next). */ db_result * db_reset_next_entry(char * table_name, db_next_desc * next_desc) { db_result * safety = empty_result(DB_SUCCESS); db * dbase = InUseDictionary->find_table(table_name); if (dbase != NULL) { if (safety) delete safety; return (dbase->execute(DB_RESET_NEXT, NULL, NULL, next_desc)); } else return (set_result(safety, DB_BADTABLE)); } /* * Returns copies of entries that satisfy the given attributes from table. * Returns the status and entries in a db_result structure. * If no attributes are specified, DB_BADQUERY is returned. */ db_result * __db_list_entries(char * table_name, int numattrs, nis_attr * attrname, bool_t useDeferred) { db_result * safety = empty_result(DB_SUCCESS); db_table_desc * tbl = NULL; db * dbase = InUseDictionary->find_table(table_name, &tbl, useDeferred); if (tbl == NULL || dbase == NULL) return (set_result(safety, DB_BADTABLE)); else { db_result * res = NULL; if (numattrs != 0) { db_query *query; query = InUseDictionary->translate_to_query(tbl, numattrs, attrname); if (query == NULL) return (set_result(safety, DB_BADQUERY)); res = dbase->execute(DB_LOOKUP, query, NULL, NULL); delete query; } else { res = dbase->execute(DB_ALL, NULL, NULL, NULL); } if (safety) delete safety; return (res); } } db_result * db_list_entries(char *table_name, int numattrs, nis_attr *attrname) { return (__db_list_entries(table_name, numattrs, attrname, TRUE)); } /* * Input: A fully qualified object name (example: "x.y.z"). * Output: Returns the first level of the object name ("x"). * If 'tableP' is non-NULL, '*tableP' will contain * the internal table name for "y.z". * * Both the return value and '*tableP' must be freed by the caller. */ char * entryName(const char *msg, char *objName, char **tableP) { char *name, *table, *dir; const char *myself = "entryName"; if (msg == 0) msg = myself; name = sdup(msg, T, objName); if (name == 0) return (0); dir = strchr(name, '.'); if (dir == 0) { sfree(name); return (0); } *(dir++) = '\0'; if (tableP == 0) return (name); table = internalTableName(dir); if (table == 0) { sfree(name); return (0); } *tableP = table; return (name); } #define RETSTAT(obj, status) \ { \ if (statP != 0) \ *statP = status; \ return (obj); \ } /* * Given a fully qualified object name, retrive a copy of the object, * using the NIS+ DB only (i.e., no LDAP). Avoids using nis_leaf_of() * etc., since they aren't re-entrant. */ nis_object * dbFindObject(char *objName, db_status *statP) { char buf[MAXPATHLEN+NIS_MAXNAMELEN+1]; char *name, *table = 0; nis_attr attr; db *dbase; db_result *res; db_table_desc *tbl = 0; db_query *query; db_mindex *mindex; nis_object *o; int lstat; const char *myself = "dbFindObject"; if (objName == 0) RETSTAT(0, DB_BADQUERY); /* The root dir is treated specially */ table = internalTableName(objName); if (table == 0) RETSTAT(0, DB_BADQUERY); if (strcmp(ROOTDIRFILE, table) == 0) { sfree(table); o = get_root_object(); if (o == 0) RETSTAT(0, DB_NOTFOUND); RETSTAT(o, DB_SUCCESS); } /* If not the root dir, find the directory where the entry lives */ sfree(table); name = entryName(myself, objName, &table); if (name == 0 || table == 0) { sfree(name); RETSTAT(0, DB_MEMORY_LIMIT); } dbase = InUseDictionary->find_table_noLDAP(table, &tbl, TRUE, TRUE); sfree(table); if (dbase != 0) mindex = dbase->mindex(); if (dbase == 0 || tbl == 0 || mindex == 0) { sfree(name); RETSTAT(0, DB_BADTABLE); } WRITELOCKNR(mindex, lstat, "mindex w dbFindObject"); if (lstat != 0) { sfree(name); RETSTAT(0, DB_LOCK_ERROR); } attr.zattr_ndx = (char *)"name"; attr.zattr_val.zattr_val_val = name; attr.zattr_val.zattr_val_len = slen(name) + 1; query = InUseDictionary->translate_to_query(tbl, 1, &attr); if (query == 0) { sfree(name); WRITEUNLOCKNR(mindex, lstat, "mindex wu dbFindObject"); RETSTAT(0, DB_BADQUERY); } /* Only want to look in the local DB */ mindex->setNoLDAPquery(); res = dbase->execute(DB_LOOKUP, query, 0, 0); mindex->clearNoLDAPquery(); delete query; sfree(name); WRITEUNLOCKNR(mindex, lstat, "mindex wu dbFindObject"); if (lstat != 0) { db_free_result(res); RETSTAT(0, DB_LOCK_ERROR); } if (res == 0) RETSTAT(0, DB_MEMORY_LIMIT); if (res->status != DB_SUCCESS) { db_status st = res->status; db_free_result(res); RETSTAT(0, st); } if (res->objects.objects_len != 1 || res->objects.objects_val == 0 || res->objects.objects_val[0] == 0) { db_free_result(res); RETSTAT(0, DB_BADOBJECT); } o = unmakePseudoEntryObj(res->objects.objects_val[0], 0); db_free_result(res); if (o == 0) { RETSTAT(0, DB_BADOBJECT); } RETSTAT(o, DB_SUCCESS); } /* * Return the object specified by 't' or 'objName' from LDAP. Set * the LDAP status in '*statP'. */ nis_object * ldapFindObj(__nis_table_mapping_t *t, char *objName, int *statP) { nis_object *o; int stat; const char *myself = "ldapFindObj"; if (t == 0) { char *table, tbuf[MAXPATHLEN + NIS_MAXNAMELEN + 1]; if (objName == 0) { if (statP != 0) *statP = LDAP_PARAM_ERROR; return (0); } /* Look for mapping */ table = internal_table_name(objName, tbuf); if (table == 0) { if (statP != 0) *statP = LDAP_PARAM_ERROR; return (0); } t = (__nis_table_mapping_t *)__nis_find_item_mt(table, &ldapMappingList, 0, 0); if (t == 0) { /* Not really an error; just not mapped */ *statP = LDAP_SUCCESS; return (0); } } o = 0; stat = objFromLDAP(t, &o, 0, 0); if (statP != 0) *statP = stat; return (o); } /* * Look for the specified object, first locally, then in LDAP. */ nis_object * findObj(char *name, db_status *statP, int *lstatP) { nis_object *o; db_status stat = DB_SUCCESS; int lstat = LDAP_SUCCESS; const char *myself = "findObj"; o = dbFindObject(name, &stat); if (o == 0) { if (stat != DB_NOTFOUND) logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: DB error %d looking for \"%s\"", myself, stat, NIL(name)); o = ldapFindObj(0, name, &lstat); if (o == 0) { if (lstat != LDAP_SUCCESS && lstat != LDAP_NO_SUCH_OBJECT) logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: LDAP error looking for \"%s\": %s", myself, NIL(name), ldap_err2string(lstat)); } } if (statP != 0) *statP = stat; if (lstatP != 0) *lstatP = lstat; return (o); } /* * Delete the specified object from the local DB. */ db_status dbDeleteObj(char *objName) { nisdb_tsd_t *tsd = __nisdb_get_tsd(); nis_object *o; db_status stat; nisdb_obj_del_t *nod, *tmp; int xid; const char *myself = "dbDeleteObj"; if (objName == 0) return (DB_SUCCESS); /* * Since in-structure locks can't completely protect * during structure deletion, we just note that the * object should be deleted, and leave that for a * (slightly) later time in rpc.nisd, where we can * use the rpc.nisd's table/directory locks for * protection. */ if (tsd == 0) return (DB_INTERNAL_ERROR); o = dbFindObject(objName, &stat); if (o == 0) { if (stat == DB_NOTFOUND) return (DB_SUCCESS); else return (stat); } /* * In order to prevent a chicken-and-egg problem (if the * object doesn't exist in LDAP, is that because we just * haven't written it to LDAP yet, or because it's been * removed), we only allow object deletion if we're the * master for it. */ nod = (nisdb_obj_del_t *)am(myself, sizeof (*nod)); if (nod == 0) { nis_destroy_object(o); return (DB_MEMORY_LIMIT); } nod->objType = o->zo_data.zo_type; nis_destroy_object(o); nod->objName = sdup(myself, T, objName); if (nod->objName == 0) { sfree(nod); return (DB_MEMORY_LIMIT); } /* Check for a dup */ for (tmp = tsd->objDelList; tmp != 0; tmp = (nisdb_obj_del_t *)tmp->next) { if (strcmp(nod->objName, tmp->objName) == 0) { sfree(nod->objName); sfree(nod); return (DB_SUCCESS); } } /* Insert at start of list */ nod->next = tsd->objDelList; tsd->objDelList = nod; return (DB_SUCCESS); } /* * Touch (i.e., update the expiration time for) the specified object. */ db_status dbTouchObj(char *objName) { char *ent, *table; db *dbase; db_table_desc *tbl = 0; db_mindex *mindex; nis_attr attr; db_query *query; db_status stat; const char *myself = "dbTouchObj"; table = internalTableName(objName); if (table == 0) return (DB_BADQUERY); if (strcmp(ROOTDIRFILE, table) == 0) { sfree(table); if (touchRootDir() == 0) return (DB_SUCCESS); else return (DB_INTERNAL_ERROR); } sfree(table); table = 0; ent = entryName(myself, objName, &table); if (ent == 0 || table == 0) { sfree(ent); return (DB_MEMORY_LIMIT); } dbase = InUseDictionary->find_table(table, &tbl, TRUE); if (dbase != 0) mindex = dbase->mindex(); if (dbase == 0 || tbl == 0 || mindex == 0) { sfree(ent); sfree(table); return (DB_BADTABLE); } attr.zattr_ndx = (char *)"name"; attr.zattr_val.zattr_val_val = ent; attr.zattr_val.zattr_val_len = slen(ent) + 1; query = InUseDictionary->translate_to_query(tbl, 1, &attr); if (query == 0) { sfree(ent); sfree(table); return (DB_BADQUERY); } mindex->touchEntry(query); sfree(ent); sfree(table); delete query; return (DB_SUCCESS); } /* * Create a NIS_TABLE_OBJ. * Borrows heavily from rpc.nisd/nis_db.c:__create_table(). */ db_status dbCreateTable(char *intName, nis_object *obj) { table_col tc[NIS_MAXCOLUMNS+1]; table_obj tobj, *t; int i; const char *myself = "dbCreateTable"; if (intName == 0 || obj == 0) return (DB_BADTABLE); t = &(obj->TA_data); /* Make sure there are searchable columns */ for (i = 0; i < t->ta_cols.ta_cols_len; i++) { if (t->ta_cols.ta_cols_val[i].tc_flags & TA_SEARCHABLE) break; } if (i >= t->ta_cols.ta_cols_len) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: No searchable columns in \"%s\" (\"%s\")", myself, NIL(obj->zo_name), NIL(intName)); return (DB_BADTABLE); } tobj = *t; /* Shift columns one step right */ for (i = 0; i < tobj.ta_cols.ta_cols_len; i++) { tc[i+1] = tobj.ta_cols.ta_cols_val[i]; } tc[0].tc_name = 0; tc[0].tc_flags = TA_XDR | TA_BINARY; tc[0].tc_rights = 0; tobj.ta_cols.ta_cols_len += 1; tobj.ta_cols.ta_cols_val = tc; return (db_create_table(intName, &tobj)); } #define TABLE_COL(o, n) o->TA_data.ta_cols.ta_cols_val[n] /* * Refresh (if necessary, create), the specified object in the local DB. */ db_status dbRefreshObj(char *name, nis_object *o) { char *objName; __nis_buffer_t b = {0, 0}; nis_object *curObj; db_status stat; char *ent, *table, *objTable; int rstat, isDir = 0, isTable = 0; const char *myself = "refreshObj"; if (o == 0) /* Delete it */ return (dbDeleteObj(name)); /* We don't work on entry objects */ if (o->zo_data.zo_type == NIS_ENTRY_OBJ) return (DB_BADOBJECT); if (name != 0) objName = name; else { bp2buf(myself, &b, "%s.%s", NIL(o->zo_name), NIL(o->zo_domain)); objName = b.buf; } curObj = dbFindObject(objName, &stat); if (curObj == 0 && stat != DB_NOTFOUND) { sfree(b.buf); return (stat); } /* * If the object doesn't change, just touch it to update the * expiration time. */ if (curObj != 0) { if (sameNisPlusObj(o, curObj)) { sfree(b.buf); nis_destroy_object(curObj); return (dbTouchObj(objName)); } /* Otherwise, check that the name and type is the same */ if (o->zo_data.zo_type != curObj->zo_data.zo_type || o->zo_name == 0 || curObj->zo_name == 0 || o->zo_domain == 0 || curObj->zo_domain == 0 || strcmp(o->zo_name, curObj->zo_name) != 0 || strcmp(o->zo_domain, curObj->zo_domain) != 0) { sfree(b.buf); nis_destroy_object(curObj); return (DB_BADOBJECT); } /* * If the object is a table, we can't allow the scheme * to change. */ if (o->zo_data.zo_type == NIS_TABLE_OBJ) { int i; if (o->TA_data.ta_maxcol != curObj->TA_data.ta_maxcol) { sfree(b.buf); nis_destroy_object(curObj); return (DB_BADOBJECT); } for (i = 0; i < o->TA_data.ta_maxcol; i++) { if ((TABLE_COL(o, i).tc_flags & TA_SEARCHABLE) != (TABLE_COL(curObj, i).tc_flags & TA_SEARCHABLE)) { sfree(b.buf); nis_destroy_object(curObj); return (DB_BADOBJECT); } } } } else { /* * If we're creating a directory object, make a note * so that we can add it to the serving list and create * the disk file. Similarly, if creating a table, we * also need to create the disk file. */ if (o->zo_data.zo_type == NIS_DIRECTORY_OBJ) isDir = 1; else if (o->zo_data.zo_type == NIS_TABLE_OBJ) isTable = 1; } objTable = internalTableName(objName); if (objTable == 0) { sfree(b.buf); if (curObj != 0) nis_destroy_object(curObj); return (DB_BADQUERY); } if (strcmp(ROOTDIRFILE, objTable) == 0) { sfree(objTable); rstat = update_root_object((nis_name)ROOTOBJFILE, o); if (rstat == 1) stat = DB_SUCCESS; else stat = DB_INTERNAL_ERROR; } else { nis_attr attr; entry_object *e, eo; entry_col ec[2]; db *dbase; db_table_desc *tbl = 0; db_mindex *mindex; db_result *dbres; int lstat; /* Find parent */ ent = entryName(myself, objName, &table); if (ent == 0 || table == 0) { sfree(b.buf); sfree(objTable); sfree(ent); if (curObj != 0) nis_destroy_object(curObj); return (DB_MEMORY_LIMIT); } /* * Calling vanilla find_table() here (which might go to * LDAP and recurse back to ourselves) so that it should * work to create a hierarchy of directories. */ dbase = InUseDictionary->find_table(table, &tbl, TRUE); if (dbase != 0) mindex = dbase->mindex(); if (dbase == 0 || tbl == 0 || mindex == 0) { sfree(b.buf); sfree(objTable); sfree(ent); sfree(table); if (curObj != 0) nis_destroy_object(curObj); return (DB_BADTABLE); } /* Construct suitable nis_attr and entry_object */ attr.zattr_ndx = (char *)"name"; attr.zattr_val.zattr_val_val = ent; attr.zattr_val.zattr_val_len = slen(ent) + 1; ec[1].ec_flags = 0; ec[1].ec_value.ec_value_val = ent; ec[1].ec_value.ec_value_len = attr.zattr_val.zattr_val_len; eo.en_type = (char *)"IN_DIRECTORY"; eo.en_cols.en_cols_val = ec; eo.en_cols.en_cols_len = 2; e = makePseudoEntryObj(o, &eo, 0); if (e == 0) { sfree(objTable); sfree(table); sfree(ent); if (curObj != 0) nis_destroy_object(curObj); return (DB_INTERNAL_ERROR); } /* Only want to update the local DB */ WRITELOCKNR(mindex, lstat, "mindex w dbRefreshObj"); if (lstat != 0) { sfree(objTable); sfree(table); sfree(ent); if (curObj != 0) nis_destroy_object(curObj); return (DB_LOCK_ERROR); } mindex->setNoWriteThrough(); mindex->setNoLDAPquery(); dbres = db_add_entry_x(table, 1, &attr, e, 0, 0); mindex->clearNoLDAPquery(); mindex->clearNoWriteThrough(); WRITEUNLOCKNR(mindex, lstat, "mindex wu dbRefreshObj"); if (lstat != 0) { sfree(objTable); sfree(table); sfree(ent); if (curObj != 0) nis_destroy_object(curObj); db_free_result(dbres); return (DB_LOCK_ERROR); } sfree(ent); sfree(table); if (dbres == 0) stat = DB_MEMORY_LIMIT; else stat = dbres->status; db_free_result(dbres); /* * If successful so far, add the transaction. */ if (stat == DB_SUCCESS) { int xid, st; db_status ds; nis_object *dirObj; /* Find the directory where this is added */ dirObj = dbFindObject(o->zo_domain, &ds); if (dirObj == 0) { sfree(objTable); if (curObj != 0) nis_destroy_object(curObj); return (ds); } xid = beginTransaction(); if (xid == 0) { sfree(objTable); if (curObj != 0) nis_destroy_object(curObj); nis_destroy_object(dirObj); return (DB_INTERNAL_ERROR); } st = addUpdate((curObj == 0) ? ADD_NAME : MOD_NAME_NEW, objName, 0, 0, o, curObj, 0); if (st != 0) { (void) abort_transaction(xid); sfree(objTable); if (curObj != 0) nis_destroy_object(curObj); nis_destroy_object(dirObj); return (DB_INTERNAL_ERROR); } st = endTransaction(xid, dirObj); if (st != 0) stat = DB_INTERNAL_ERROR; if (curObj != 0) nis_destroy_object(curObj); nis_destroy_object(dirObj); } /* * If it's a table or directory, create the DB file. * If a directory, also add it to the serving list. */ if (stat == DB_SUCCESS &&(isDir || isTable)) { if (isDir) { stat = db_create_table(objTable, &tbl_prototype); } else { stat = dbCreateTable(objTable, o); } } sfree(objTable); } sfree(b.buf); return (stat); } /* * Replace the object stored with the mapping 't'. Return TRUE if * at least one object was replaced, FALSE otherwise. */ bool_t replaceMappingObj(__nis_table_mapping_t *t, nis_object *n) { __nis_table_mapping_t *x; nis_object *old = 0; int assigned = 0; /* * The alternate mappings are usually mostly copies * of the original, so we try to make sure that we * don't free the same nis_object twice. */ for (x = t; x != 0; x = (__nis_table_mapping_t *)x->next) { if (old == 0) { old = x->obj; if (x->obj != 0) nis_destroy_object(x->obj); } else { if (x->obj != old && x->obj != 0) nis_destroy_object(x->obj); } x->obj = n; assigned++; } return (assigned > 0); } /* * Set object type, column info, and obj for the specified * mapping 't' from the object 'o'. Returns zero if 'o' was unused, * and should be freed by the caller, larger than zero otherwise. */ int setMappingObjTypeEtc(__nis_table_mapping_t *t, nis_object *o) { __nis_table_mapping_t *x; int ls, ret; int i; if (t == 0 || o == 0) return (0); t->objType = o->zo_data.zo_type; for (x = t; x != 0; x = (__nis_table_mapping_t *)x->next) { if (x != t) { x->objType = t->objType; } if (x->objType == NIS_TABLE_OBJ) { /* * If we have rules, this mapping is for table entries, * and we need the column names. Otherwise, remove the * column names (if any). */ for (i = 0; i < x->numColumns; i++) sfree(x->column[i]); sfree(x->column); x->column = 0; x->numColumns = 0; } } ret = replaceMappingObj(t, o); return (ret); } /* * Retrieve the specified object (internal DB name) from LDAP, and * refresh/create as appropriate. */ db_status dbCreateFromLDAP(char *intName, int *ldapStat) { __nis_table_mapping_t *t; int lstat, doDestroy; nis_object *obj = 0; db_status dstat; const char *myself = "dbCreateFromLDAP"; if (!useLDAPrespository) { if (ldapStat != 0) *ldapStat = LDAP_SUCCESS; return (DB_SUCCESS); } t = (__nis_table_mapping_t *)__nis_find_item_mt(intName, &ldapMappingList, 0, 0); /* No mapping isn't a failure */ if (t == 0) { if (ldapStat != 0) *ldapStat = LDAP_SUCCESS; return (DB_NOTFOUND); } lstat = objFromLDAP(t, &obj, 0, 0); if (ldapStat != 0) *ldapStat = lstat; if (lstat != LDAP_SUCCESS) return (DB_NOTFOUND); /* * If the LDAP operation was successful, but 'obj' is NULL, * there's no mapping for this object, and we're done. */ if (obj == 0) return (DB_SUCCESS); /* Update the mapping with object info */ doDestroy = setMappingObjTypeEtc(t, obj) == 0; dstat = dbRefreshObj(t->objName, obj); if (doDestroy) nis_destroy_object(obj); return (dstat); } /* * Up- (fromLDAP==0) or down- (fromLDAP==1) load all LDAP mapped data. * Returns an LDAP error status. */ int loadAllLDAP(int fromLDAP, void *cookie, db_status *dstatP) { __nis_table_mapping_t *t, *start; int stat = LDAP_SUCCESS; db_status dstat = DB_SUCCESS; db *dbase; db_table_desc *tbl = 0; db_mindex *mindex; const char *myself = "loadAllLDAP"; /* * If the 'cookie' and '*cookie' are non-NULL, start scanning * the mappings from '*cookie'. When we return with an error, * we set '*cookie' to point to the mapping being processed. * This enables our caller to react appropriately, and retry * if desired. * * The cookie is opaque to our caller, who's only allowed to * initialize *cookie to NULL. */ if (cookie != 0) { start = *((__nis_table_mapping_t **)cookie); if (start == 0) start = ldapMappingSeq; } else { start = ldapMappingSeq; } for (t = start; t != 0; t = (__nis_table_mapping_t *)t->seqNext) { __nis_table_mapping_t **tp; int nm; if (fromLDAP) { /* Are there any mappings for the object proper ? */ tp = selectTableMapping(t, 0, 0, 1, t->dbId, &nm); if (tp != 0 && nm > 0) { dstat = dbCreateFromLDAP(t->objPath, &stat); if (dstat != DB_SUCCESS) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: DB error %d creating \"%s\": %s", myself, dstat, NIL(t->objName), ldap_err2string(stat)); if (cookie != 0) *((__nis_table_mapping_t **) cookie) = t; if (dstatP != 0) *dstatP = dstat; else if (stat == LDAP_SUCCESS) stat = LDAP_OPERATIONS_ERROR; sfree(tp); return (stat); } } sfree(tp); /* Any mappings for table entries ? */ tp = selectTableMapping(t, 0, 0, 0, t->dbId, &nm); if (tp == 0 || nm <= 0) { sfree(tp); continue; } sfree(tp); /* * The object itself must exist in the local * DB by now. Get the db_mindex and let * db_mindex::queryLDAP() do the work; if * the object isn't a table, queryLDAP() * will do nothing and return success. */ dbase = InUseDictionary->find_table(t->objPath, &tbl, TRUE); if (dbase != 0) mindex = dbase->mindex(); if (dbase == 0 || tbl == 0 || mindex == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: No local DB entry for \"%s\" (%s:%s)", myself, NIL(t->objPath), NIL(t->dbId), NIL(t->objName)); if (cookie != 0) *((__nis_table_mapping_t **)cookie) = t; if (dstatP != 0) *dstatP = DB_BADTABLE; return ((dstatP != 0) ? LDAP_SUCCESS : LDAP_OPERATIONS_ERROR); } mindex->setInitialLoad(); stat = mindex->queryLDAP(0, t->dbId, 0); mindex->clearInitialLoad(); if (stat != LDAP_SUCCESS) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: LDAP error retrieving entries for %s:%s: %s", myself, NIL(t->dbId), NIL(t->objName), ldap_err2string(stat)); if (cookie != 0) *((__nis_table_mapping_t **)cookie) = t; if (dstatP != 0) *dstatP = DB_SUCCESS; return (stat); } } else { nis_object *obj; char *ent, *objPath; int freeObjPath = 0; /* * Up-loading to LDAP, so the object must * already exist in the local DB. */ obj = dbFindObject(t->objName, &dstat); if (obj == 0) { if (dstat == DB_NOTFOUND) logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: No local DB object for \"%s\" (%s:%s); skipping up-load", myself, NIL(t->objPath), NIL(t->dbId), NIL(t->objName)); else logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: DB error %d for \"%s\" (%s:%s); skipping up-load", myself, dstat, NIL(t->objPath), NIL(t->dbId), NIL(t->objName)); continue; } /* * If it's a table or directory, there will be * a dictionary entry for the object itself. * Otherwise, we need the dictionary entry for * the parent directory. * * For a table, we need the db_mindex for both the * table object itself, as well as for the parent * directory (in order to store table entries). * We start with the latter. */ if (obj->zo_data.zo_type == NIS_DIRECTORY_OBJ) { objPath = t->objPath; ent = 0; } else { objPath = 0; ent = entryName(myself, t->objName, &objPath); if (ent == 0 || objPath == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error deriving entry/DB-table names for %s:%s; skipping up-load", myself, NIL(t->dbId), NIL(t->objName)); sfree(ent); sfree(objPath); nis_destroy_object(obj); obj = 0; continue; } freeObjPath = 1; } dbase = InUseDictionary->find_table(objPath, &tbl, TRUE); if (dbase != 0) mindex = dbase->mindex(); if (dbase == 0 || tbl == 0 || mindex == 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: No local DB entry for \"%s\" (%s:%s); skipping up-load", myself, objPath, NIL(t->dbId), NIL(t->objName)); sfree(ent); if (freeObjPath) sfree(objPath); nis_destroy_object(obj); obj = 0; continue; } /* * Our next action(s) depend on the object type: * * directory Store dir object * * table Store table obj, as well * as any entries in the * table * * other Store object; we need to * build a db_query specifying * the first-level name of the * object. * * storeLDAP() will just do nothing and return * success if we try to, say, store a table object * when only the table entries are mapped. Hence, * we don't have to worry about those distinctions * here. */ if (obj->zo_data.zo_type == NIS_DIRECTORY_OBJ) { stat = mindex->storeLDAP(0, 0, obj, 0, t->dbId); } else { nis_attr attr; db_query *q; attr.zattr_ndx = (char *)"name"; attr.zattr_val.zattr_val_val = ent; attr.zattr_val.zattr_val_len = slen(ent) + 1; q = new db_query(mindex->getScheme(), 1, &attr); if (q == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: error creating db_query for \"%s\" in \"%s\"; skipping up-load", myself, ent, objPath); sfree(ent); if (freeObjPath) sfree(objPath); nis_destroy_object(obj); obj = 0; continue; } stat = mindex->storeLDAP(q, 0, obj, 0, t->dbId); delete q; } sfree(ent); if (freeObjPath) sfree(objPath); if (stat != LDAP_SUCCESS) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error storing %s:%s to LDAP: %s", myself, NIL(t->dbId), NIL(t->objName), ldap_err2string(stat)); nis_destroy_object(obj); obj = 0; if (cookie != 0) *((__nis_table_mapping_t **) cookie) = t; if (dstatP != 0) *dstatP = DB_SUCCESS; return (stat); } /* Any mappings for table entries ? */ tp = selectTableMapping(t, 0, 0, 0, t->dbId, &nm); if (tp == 0 || nm <= 0) { sfree(tp); nis_destroy_object(obj); obj = 0; continue; } sfree(tp); /* * If it's a table, we also need to store the table * entries. */ if (obj->zo_data.zo_type == NIS_TABLE_OBJ) { tbl = 0; dbase = InUseDictionary->find_table(t->objPath, &tbl, TRUE); if (dbase != 0) mindex = dbase->mindex(); if (dbase == 0 || tbl == 0 || mindex == 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: No local DB entry for \"%s\" (%s:%s); skipping entry up-load", myself, NIL(t->objPath), NIL(t->dbId), NIL(t->objName)); nis_destroy_object(obj); obj = 0; continue; } stat = mindex->storeLDAP(0, 0, obj, 0, t->dbId); if (stat != LDAP_SUCCESS) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Error storing %s:%s entries to LDAP: %s", myself, NIL(t->dbId), NIL(t->objName), ldap_err2string(stat)); nis_destroy_object(obj); obj = 0; if (cookie != 0) *((__nis_table_mapping_t **) cookie) = t; if (dstatP != 0) *dstatP = DB_SUCCESS; return (stat); } } nis_destroy_object(obj); obj = 0; } } if (dstatP != 0) *dstatP = dstat; return (stat); } /* * Object identified by given attribute name is added to specified table. * If object already exists, it is replaced. If more than one object * matches the given attribute name, DB_NOTUNIQUE is returned. */ static db_result * db_add_entry_x(char * tab, int numattrs, nis_attr * attrname, entry_obj * newobj, int skiplog, int nosync) { db_result * safety = empty_result(DB_SUCCESS); db_table_desc * tbl = NULL; db * dbase = InUseDictionary->find_table(tab, &tbl, FALSE); if (tbl == NULL || dbase == NULL) { return (set_result(safety, DB_BADTABLE)); } else if (skiplog) { db_result * res; res = dbase->execute(DB_ADD_NOLOG, NULL, (entry_object *) newobj, NULL); if (safety) delete safety; return (res); } else { db_result *res; db_query * query = InUseDictionary->translate_to_query(tbl, numattrs, attrname); if (query == NULL) return (set_result(safety, DB_BADQUERY)); if (nosync) res = dbase->execute(DB_ADD_NOSYNC, query, (entry_object *) newobj, NULL); else res = dbase->execute(DB_ADD, query, (entry_object *) newobj, NULL); delete query; if (safety) delete safety; return (res); } } db_result * db_add_entry(char * tab, int numattrs, nis_attr * attrname, entry_obj * newobj) { return (db_add_entry_x(tab, numattrs, attrname, newobj, 0, 0)); } db_result * __db_add_entry_nolog(char * tab, int numattrs, nis_attr * attrname, entry_obj * newobj) { return (db_add_entry_x(tab, numattrs, attrname, newobj, 1, 0)); } db_result * __db_add_entry_nosync(char * tab, int numattrs, nis_attr * attrname, entry_obj * newobj) { return (db_add_entry_x(tab, numattrs, attrname, newobj, 0, 1)); } /* * Remove object identified by given attributes from specified table. * If no attribute is supplied, all entries in table are removed. * If attributes identify more than one object, all objects are removed. */ db_result * db_remove_entry_x(char * table_name, int num_attrs, nis_attr * attrname, int nosync) { db_result * safety = empty_result(DB_SUCCESS); db_table_desc * tbl = NULL; db * dbase = InUseDictionary->find_table(table_name, &tbl, FALSE); db_result * res; if (tbl == NULL || dbase == NULL) return (set_result(safety, DB_BADTABLE)); else { if (num_attrs != 0) { db_query *query; query = InUseDictionary->translate_to_query(tbl, num_attrs, attrname); if (query == NULL) return (set_result(safety, DB_BADQUERY)); if (nosync) res = dbase->execute(DB_REMOVE_NOSYNC, query, NULL, NULL); else res = dbase->execute(DB_REMOVE, query, NULL, NULL); delete query; } else { if (nosync) res = dbase->execute(DB_REMOVE_NOSYNC, NULL, NULL, NULL); else res = dbase->execute(DB_REMOVE, NULL, NULL, NULL); } if (safety) delete safety; return (res); } } db_result * db_remove_entry(char * table_name, int num_attrs, nis_attr * attrname) { return (db_remove_entry_x(table_name, num_attrs, attrname, 0)); } db_result * __db_remove_entry_nosync(char * table_name, int num_attrs, nis_attr * attrname) { return (db_remove_entry_x(table_name, num_attrs, attrname, 1)); } /* Return a copy of the version of specified table. */ vers * db_version(char * table_name) { db * dbase = InUseDictionary->find_table(table_name); if (dbase == NULL) return (NULL); vers* v = new vers(dbase->get_version()); if (v == NULL) WARNING("nis_db::db_version: cannot allocate space"); return (v); } /* Return log entries since (later than) given version 'v' of table. */ db_log_list * db_log_entries_since(char * table_name, vers * v) { db * dbase = InUseDictionary->find_table(table_name); if (dbase == NULL) return (NULL); return (dbase->get_log_entries_since(v)); } db_status db_sync_log(char *table_name) { db * dbase = InUseDictionary->find_table(table_name); if (dbase == NULL) return (DB_BADTABLE); return (dbase->sync_log()); } /* * Apply the given update specified in 'entry' to the specified table. * Returns DB_SUCCESS if update was executed. * Returns DB_NOTFOUND if update occurs too early to be applied. */ db_status db_apply_log_entry(char * table_name, db_log_entry * entry) { db * dbase = InUseDictionary->find_table(table_name, NULL, FALSE); if (dbase == NULL) return (DB_BADTABLE); if (dbase->execute_log_entry(entry)) return (DB_SUCCESS); /* got executed */ else return (DB_NOTFOUND); /* not executed */ } /* * Checkpoint specified table (i.e. incorporate logged updates to main * database file). If table_name is NULL, checkpoint all tables that * needs it. */ db_status db_checkpoint(char * table_name) { return (InUseDictionary->db_checkpoint(table_name)); } /* Print names of tables in system. */ void db_print_table_names() { int i; db_table_names * answer = InUseDictionary->get_table_names(); if (answer != NULL) { for (i = 0; i < answer->db_table_names_len; i++) { printf("%s\n", answer->db_table_names_val[i]); delete answer->db_table_names_val[i]; } delete answer->db_table_names_val; delete answer; } } /* Print statistics of specified table to stdout. */ db_status db_stats(char * table_name) { db_table_desc * tbl = NULL; db *dbase = InUseDictionary->find_table(table_name, &tbl); if (tbl == NULL || dbase == NULL || tbl->scheme == NULL) return (DB_BADTABLE); dbase->print(); tbl->scheme->print(); return (DB_SUCCESS); } /* Print statistics of indices of specified table to stdout. */ db_status db_print_all_indices(char * table_name) { db * dbase = InUseDictionary->find_table(table_name); if (dbase == NULL) return (DB_BADTABLE); dbase->print_all_indices(); return (DB_SUCCESS); } /* Print specified index of table to stdout. */ db_status db_print_index(char * table_name, int which) { db * dbase = InUseDictionary->find_table(table_name); if (dbase == NULL) return (DB_BADTABLE); dbase->print_index(which); return (DB_SUCCESS); } /* close open files */ db_status db_standby(char * table_name) { return (InUseDictionary->db_standby(table_name)); } /* Returns DB_SUCCESS if table exists; DB_BADTABLE if table does not exist. */ db_status db_table_exists(char * table_name) { db_table_desc *dbtab = InUseDictionary->find_table_desc(table_name); if (dbtab == NULL) return (DB_BADTABLE); return (DB_SUCCESS); } /* * Returns DB_SUCCESS if table exists; DB_BADTABLE if table does not exist. * If table already loaded, unload it. */ db_status db_unload_table(char * table_name) { db_table_desc * dbtab = InUseDictionary->find_table_desc(table_name); if (dbtab == NULL) return (DB_BADTABLE); // unload if (dbtab->database != NULL) { delete dbtab->database; dbtab->database = NULL; } return (DB_SUCCESS); } /* * Put the specified table in deferred mode, which means that updates go * to the original table, but reads are satisfied out of a copy (which we * make here). Thus, "defer" refers to the table as seen by read requests, * since for them, changes are deferred. */ db_status __db_defer(char *table_name) { db_status stat; stat = InUseDictionary->defer(table_name); return (stat); } /* * Commit deferred changes for the specified table. I.e., make visible * any updates made since the table was deferred. */ db_status __db_commit(char *table_name) { db_status stat; stat = InUseDictionary->commit(table_name); return (stat); } /* * Rollback, i.e., return to the state before we entered deferred mode. */ db_status __db_rollback(char *table_name) { db_status stat; stat = InUseDictionary->rollback(table_name); return (stat); } db_status __db_configure(char *table_name) { db_status stat; char tablePath[MAXPATHLEN + NIS_MAXNAMELEN + 1]; db *dbase = InUseDictionary->find_table(table_name, NULL); if (dbase == NULL || table_name == 0) return (DB_BADTABLE); if (strlen(table_name) >= sizeof (tablePath)) return (DB_BADQUERY); if (internal_table_name(table_name, tablePath) == 0) return (DB_STORAGE_LIMIT); if (dbase->configure(tablePath)) stat = DB_SUCCESS; else stat = DB_INTERNAL_ERROR; return (stat); } /* * During some rpc.nisd operations (such as when recovering the trans.log), * we don't want to use the LDAP repository, so we provide a main switch. * Note that we expect this to be used only when rpc.nisd is single-threaded, * so there is no need for synchronization when reading or modifying the * value of the main switch. */ int useLDAPrespository = 1; void __db_disallowLDAP(void) { useLDAPrespository = 0; } void __db_allowLDAP(void) { useLDAPrespository = 1; } } /* extern "C" */