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