/* * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * DESCRIPTION: Contains the top level shim hook functions. These must have * identical interfaces to the equivalent standard dbm calls. * * Unfortunately many of these will do a copy of a datum structure * on return. This is a side effect of the original DBM function * being written to pass structures rather than pointers. * * NOTE : There is a major bug/feature in dbm. A key obtained by * dbm_nextkey() of dbm_firstkey() cannot be passed to dbm_store(). * When the store occurs dbm's internal memory get's reorganized * and the static strings pointed to by the key are destroyed. The * data is then stored in the wrong place. We attempt to get round * this by dbm_firstkey() and dbm_nextkey() making a copy of the * key data in malloced memory. This is freed when map_ctrl is * freed. */ #include #include #include #include #include "ypsym.h" #include "ypdefs.h" #include "shim.h" #include "yptol.h" #include "stubs.h" #include "../ldap_parse.h" #include "../ldap_util.h" /* * Globals */ bool_t yptol_mode = FALSE; /* Set if in N2L mode */ bool_t yptol_newlock = FALSE; /* * Set if in N2L mode and we want to use the new * lock mapping mechanism */ bool_t ypxfrd_flag = FALSE; /* Set if called from ypxfrd */ pid_t parent_pid; /* ID of calling parent process */ /* * Decs */ void check_old_map_date(map_ctrl *); /* * Constants */ /* Number of times to try to update a map before giving up */ /* #define MAX_UPDATE_ATTEMPTS 3 */ #define MAX_UPDATE_ATTEMPTS 1 /* * FUNCTION: shim_dbm_close(); * * INPUTS: Identical to equivalent dbm call. * * OUTPUTS: Identical to equivalent dbm call. * */ void shim_dbm_close(DBM *db) { map_ctrl *map; /* Lock the map */ map = get_map_ctrl(db); if (map == NULL) return; free_map_ctrl(map); } /* * FUNCTION: shim_dbm_delete(); * * DESCRIPTION: This function is currently unused but is present so that the * set of shim_dbm_xxx() interfaces is complete if required in * future. * * INPUTS: Identical to equivalent dbm call. * * OUTPUTS: Identical to equivalent dbm call. * */ int shim_dbm_delete(DBM *db, datum key) { int ret; map_ctrl *map; /* Lock the map */ map = get_map_ctrl(db); if (map == NULL) return (FAILURE); if (1 != lock_map_ctrl(map)) return (FAILURE); if (yptol_mode) { /* Delete from and ttl map. Not a huge disaster if it fails. */ dbm_delete(map->ttl, key); } ret = dbm_delete(map->entries, key); unlock_map_ctrl(map); return (ret); } /* * FUNCTION: shim_dbm_fetch() * * DESCRIPTION: N2L function used to handle 'normal' dbm_fetch() operations. * * INPUTS: First two identical to equivalent dbm call. * * OUTPUTS: Identical to equivalent dbm call. * */ datum shim_dbm_fetch(DBM *db, datum key) { datum ret = {0, NULL}; map_ctrl *map; /* Lock the map */ map = get_map_ctrl(db); if (map == NULL) return (ret); if (1 != lock_map_ctrl(map)) return (ret); if (yptol_mode) { if (SUCCESS == update_entry_if_required(map, &key)) { /* Update thinks we should return something */ ret = dbm_fetch(map->entries, key); } } else { /* Non yptol mode do a normal fetch */ ret = dbm_fetch(map->entries, key); } unlock_map_ctrl(map); return (ret); } /* * FUNCTION: shim_dbm_fetch_noupdate() * * DESCRIPTION: A special version of shim_dbm_fetch() that never checks TTLs * or updates entries. * * INPUTS: Identical to equivalent dbm call. * * OUTPUTS: Identical to equivalent dbm call. * */ datum shim_dbm_fetch_noupdate(DBM *db, datum key) { datum ret = {0, NULL}; map_ctrl *map; /* Get the map control block */ map = get_map_ctrl(db); if (map == NULL) return (ret); /* Not updating so no need to lock */ ret = dbm_fetch(map->entries, key); return (ret); } /* * FUNCTION: shim_dbm_firstkey() * * DESCRIPTION: Get firstkey in an enumeration. If the map is out of date then * this is the time to scan it and see if any new entries have been * created. * * INPUTS: Identical to equivalent dbm call. * * OUTPUTS: Identical to equivalent dbm call. * */ datum shim_dbm_firstkey(DBM *db) { int count; bool_t wait_flag; datum ret = {0, NULL}; map_ctrl *map; /* Lock the map */ map = get_map_ctrl(db); if (map == NULL) return (ret); if (1 != lock_map_ctrl(map)) return (ret); if (yptol_mode) { /* * Due to the limitations in the hashing algorithm ypxfrd * may end up waiting on the wrong update. It must thus loop * until the right map has been updated. */ for (count = 0; has_map_expired(map) && (MAX_UPDATE_ATTEMPTS > count); count++) { /* * Ideally ypxfr should wait for the map update * to complete i.e. pass ypxfrd_flag into * update_map_if_required(). This cannot be done * because if there is a large map update the client * side, ypxfr, can time out while waiting. */ wait_flag = FALSE; update_map_if_required(map, wait_flag); if (wait_flag) { /* * Because ypxfrd does weird things with DBMs * internal structures it's a good idea to * reopen here. (Code that uses the real DBM * API appears not to need this.) * * This should not be necessary all we have * done is 'mv' the new file over the old one. * Open handles should get the old data but if * these lines are removed the first ypxfrd * read access fail with bad file handle. * * NOTE : If we don't wait, because of the * ypxfr timeout problem, there is no point * doing this. */ dbm_close(map->entries); dbm_close(map->ttl); if (FAILURE == open_yptol_files(map)) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not reopen DBM files"); } } else { /* For daemons that don't wait just try once */ break; } } if (MAX_UPDATE_ATTEMPTS < count) logmsg(MSG_NOTIMECHECK, LOG_ERR, "Cannot update map %s", map->map_name); } ret = dbm_firstkey(map->entries); /* Move key data out of static memory. See NOTE in file header above */ if (yptol_mode) { set_key_data(map, &ret); } unlock_map_ctrl(map); return (ret); } /* * FUNCTION: shim_dbm_nextkey() * * DESCRIPTION: Get next key in an enumeration. Since updating an entry would * invalidate the enumeration we never do it. * * INPUTS: Identical to equivalent dbm call. * * OUTPUTS: Identical to equivalent dbm call. * */ datum shim_dbm_nextkey(DBM *db) { datum ret; map_ctrl *map; /* Lock the map */ map = get_map_ctrl(db); if (map == NULL) return (ret); if (1 != lock_map_ctrl(map)) return (ret); ret = dbm_nextkey(map->entries); /* Move key data out of static memory. See NOTE in file header above */ if (yptol_mode) { set_key_data(map, &ret); } unlock_map_ctrl(map); return (ret); } /* * FUNCTION: shim_dbm_do_nextkey() * * DESCRIPTION: Get next key in an enumeration. Since updating an entry would * invalidate the enumeration we never do it. * * NOTE : dbm_do_nextkey is not a documented or legal DBM API. * Despite this the existing NIS code calls it. One gross hack * deserves another so we have this extra shim function to handle * the illegal call. * * INPUTS: Identical to equivalent dbm call. * * OUTPUTS: Identical to equivalent dbm call. * */ datum shim_dbm_do_nextkey(DBM *db, datum inkey) { datum ret; map_ctrl *map; /* Lock the map */ map = get_map_ctrl(db); if (map == NULL) return (ret); if (1 != lock_map_ctrl(map)) return (ret); ret = dbm_do_nextkey(map->entries, inkey); /* Move key data out of static memory. See NOTE in file header above */ if (yptol_mode) { set_key_data(map, &ret); } unlock_map_ctrl(map); return (ret); } /* * FUNCTION: shim_dbm_open() * * INPUTS: Identical to equivalent dbm call. * * OUTPUTS: Identical to equivalent dbm call. * */ DBM * shim_dbm_open(const char *file, int open_flags, mode_t file_mode) { map_ctrl *map; suc_code ret = FAILURE; /* Find or create map_ctrl for this map */ map = create_map_ctrl((char *)file); if (map == NULL) return (NULL); /* Lock map */ if (1 != lock_map_ctrl(map)) return (NULL); /* Remember flags and mode in case we have to reopen */ map->open_flags = open_flags; map->open_mode = file_mode; if (yptol_mode) { ret = open_yptol_files(map); /* * This is a good place to check that the * equivalent old style map file has not been * updated. */ if (SUCCESS == ret) check_old_map_date(map); } else { /* Open entries map */ map->entries = dbm_open(map->map_path, map->open_flags, map->open_mode); if (NULL != map->entries) ret = SUCCESS; } /* If we were not successful unravel what we have done so far */ if (ret != SUCCESS) { unlock_map_ctrl(map); free_map_ctrl(map); return (NULL); } unlock_map_ctrl(map); /* Return map_ctrl pointer as a DBM *. To the outside world it is */ /* opaque. */ return ((DBM *)map); } /* * FUNCTION: shim_dbm_store() * * DESCRIPTION: Shim for dbm_store. * * In N2L mode if we are asked to store in DBM_INSERT mode * then first an attempt is made to write to the DIT (in the same * mode). If this is successful then the value is forced into DBM * using DBM_REPLACE. This is because the DIT is authoritative. * The success of failure of an 'insert' is determined by the * presence or otherwise of an entry in the DIT not DBM. * * INPUTS: Identical to equivalent dbm call. * * OUTPUTS: Identical to equivalent dbm call. * */ int shim_dbm_store(DBM *db, datum key, datum content, int store_mode) { int ret; map_ctrl *map; /* Get map name */ map = get_map_ctrl(db); if (map == NULL) return (FAILURE); if (yptol_mode) { /* Write to the DIT before doing anything else */ if (!write_to_dit(map->map_name, map->domain, key, content, DBM_REPLACE == store_mode, FALSE)) return (FAILURE); } /* Lock the map */ if (1 != lock_map_ctrl(map)) return (FAILURE); if (yptol_mode) { if (!is_map_updating(map)) { ret = dbm_store(map->entries, key, content, DBM_REPLACE); if (SUCCESS == ret) /* Update TTL */ update_entry_ttl(map, &key, TTL_RAND); } } else { ret = dbm_store(map->entries, key, content, store_mode); } unlock_map_ctrl(map); return (ret); } /* * FUNCTION : shim_exit() * * DESCRIPTION: Intercepts exit() calls made by N2L compatible NIS components. * This is required because any call to the shim_dbm... series * of functions may have started an update thread. If the process * exits normally then this thread may be killed before it can * complete its work. We thus wait here for the thread to complete. * * GIVEN : Same arg as exit() * * RETURNS : Never */ void shim_exit(int code) { thr_join(NULL, NULL, NULL); exit(code); } /* * FUNCTION : init_yptol_flag() * * DESCRIPTION: Initializes two flags these are similar but their function is * subtly different. * * yp2ldap tells the mapping system if it is to work in NIS or * NIS+ mode. For N2L this is always set to NIS mode. * * yptol tells the shim if it is to work in N2L or traditional * NIS mode. For N2L this is turned on if the N2L mapping file * is found to be present. In NIS+ mode it is meaningless. */ void init_yptol_flag() { /* * yp2ldap is used to switch appropriate code in the * common libnisdb library used by rpc.nisd and ypserv. */ yp2ldap = 1; yptol_mode = is_yptol_mode(); /* * Use the new lock mapping mechanism * if in N2L mode. */ yptol_newlock = yptol_mode; } /* * FUNCTION : set_yxfrd_flag() */ void set_ypxfrd_flag() { ypxfrd_flag = TRUE; } /* * FUNCTION : check_old_map_date() * * DESCRIPTION: Checks that an old style map has not been updated. If it has * then ypmake has probably erroneously been run and an error is * logged. * * GIVEN : A map_ctrl containing details of the NEW STYLE map. * * RETURNS : Nothing */ void check_old_map_date(map_ctrl *map) { datum key; datum value; struct stat stats; time_t old_time; /* Get date of last update */ if (0 != stat(map->trad_map_path, &stats)) { /* * No problem. We have a new style map but no old style map * this will occur if the original data came from native LDAP * instead of NIS. */ return; } /* Set up datum with key for recorded old map update time */ key.dsize = strlen(MAP_OLD_MAP_DATE_KEY); key.dptr = MAP_OLD_MAP_DATE_KEY; value = dbm_fetch(map->ttl, key); if (NULL != value.dptr) { /* * Because dptr may not be int aligned need to build an int * out of what it points to or will get a bus error. */ bcopy(value.dptr, &old_time, sizeof (time_t)); /* Do the comparison */ if (stats.st_mtime <= old_time) { /* All is well, has not been updated */ return; } /* If we get here the file has been updated */ logmsg(MSG_NOTIMECHECK, LOG_ERR, "Caution. ypmake may have been run in N2L " "mode. This will NOT initiate a NIS map push. In " "this mode pushes should be initiated with yppush"); } /* * If we get here then either the file was updated or there was not * a valid old map date (no problem, maybe this is the first time we * checked). In either case the old map date entry must be update. */ value.dptr = (char *)&(stats.st_mtime); value.dsize = sizeof (time_t); dbm_store(map->ttl, key, value, DBM_REPLACE); } /* * FUNCTION : init_lock_system() * * DESCRIPTION: Initializes all the systems related to map locking. This must * be called before any access to the shim functions. * * GIVEN : A flag indicating if we are being called from ypserv, which does * not wait for map updates to complete, or other NIS components * which do. * * RETURNS : TRUE = Everything worked * FALSE = There were problems */ bool_t init_lock_system(bool_t ypxfrd) { /* Remember what called us */ if (ypxfrd) set_ypxfrd_flag(); /* * Remember PID of process which called us. This enables update threads * created by YP children to be handled differently to those created * by YP parents. */ parent_pid = getpid(); /* Init map locks */ if (!init_lock_map()) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "Failed to init process synchronization"); return (FALSE); } /* If we are in yptol mode set flag indicating the fact */ init_yptol_flag(); /* * If boot random number system. For now go for reproducible * random numbers. */ srand48(0x12345678); /* * If not N2L mode then no error but do not bother initializing update * flags. */ if (yptol_mode) { if (!init_update_lock_map()) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "Failed to init update synchronization"); return (FALSE); } } return (TRUE); }