OpenSolaris_b135/lib/libnisdb/yptol/shim.c

/*
 * 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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * 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 <unistd.h>
#include <syslog.h>
#include <ndbm.h>
#include <strings.h>
#include "ypsym.h"
#include "ypdefs.h"
#include "shim.h"
#include "yptol.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);
}