OpenSolaris_b135/lib/libnisdb/yptol/dit_access.c

Compare this file to the similar file:
Show the results in this format:

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

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

/*
 * DESCRIPTION: Contains top level functions to read/write to the DIT. These
 *		are the API between the shim and the mapping system.
 *		Things calling these should have no knowledge of LDAP. Things
 *		called by them should have no knowledge of NIS.
 *
 *		Error handling here may appear to be limited but, because the
 *		NIS protocol cannot carry meaningful information about why a
 *		N2L operation failed, functions that don't work log
 *		an error and then just return FAILURE.
 *
 */

/*
 * Includes. WE WANT TO USE REAL DBM FUNCTIONS SO DO NOT INCLUDE SHIM_HOOKS.H.
 */
#include <unistd.h>
#include <syslog.h>
#include <ndbm.h>
#include <sys/systeminfo.h>
#include <string.h>
#include <lber.h>
#include <ldap.h>
#include <errno.h>
#include "ypsym.h"
#include "ypdefs.h"
#include "shim.h"
#include "../ldap_structs.h"
#include "../ldap_parse.h"
#include "../nisdb_ldap.h"
#include "../ldap_util.h"
#include "../ldap_op.h"
#include "../ldap_attr.h"
#include "../nis_parse_ldap_conf.h"
#include "../nisdb_mt.h"
#include "yptol.h"
#include "dit_access_utils.h"
#include "stdio.h"

extern bool delete_map(char *name);
extern bool rename_map(char *from, char *to, bool_t secure_map);

/* Enable standard YP code features defined in ypdefs.h */
USE_YP_MASTER_NAME
USE_YP_DOMAIN_NAME
USE_YP_SECURE
USE_YP_INTERDOMAIN

/*
 * Decs
 */
suc_code add_special_entries(DBM *, map_ctrl *, bool_t *);
void free_null_terminated_list(char **list);


/*
 * FUNCTION:    is_yptol_mode();
 *
 * DESCRIPTION:	Determines if we should run in N2L or traditional mode based
 *		on the presence of the N2L mapping file. If there are problems
 *		with the file, e.g. unreadable, this will be picked up latter.
 *
 * INPUTS:     	Nothing
 *
 * OUTPUTS:   	TRUE = Run in N2L mode
 *		FALSE = Run in traditional mode.
 */
bool_t
is_yptol_mode()
{
	struct stat filestat;

	if (stat(YP_DEFAULTCONFFILE, &filestat) != -1)
		return (TRUE);

	return (FALSE);
}

/*
 * FUNCTION:    read_from_dit();
 *
 * DESCRIPTION:	Read (i.e. get and map) a single NIS entry from the LDAP DIT.
 *		Also handles retry attempts, on failure, and interpretation of
 *		internal error codes.
 *
 * INPUTS:     	Map name (unqualified)
 *		Domain name
 *		Entry key
 *		Pointer to return location
 *
 * OUTPUTS:   	If successful DBM datum containing result.
 *		On error DBM datum pointing to NULL and, if the cached value
 *		is not to be used, an error code.
 */
int
read_from_dit(char *map, char *domain, datum *key, datum *value)
{
	int count;
	int res;
	__nisdb_retry_t	*retrieveRetry;

	/* Initialize tsd */
	__nisdb_get_tsd()->domainContext = 0;
	__nisdb_get_tsd()->escapeFlag = '\0';

	for (count = 0; count < ypDomains.numDomains; count++) {
		if (0 == ypDomains.domainLabels[count])
			continue;
		if (0 == strcasecmp(domain, ypDomains.domainLabels[count])) {
			__nisdb_get_tsd()->domainContext =
			    ypDomains.domains[count];
			break;
		}
	}

	retrieveRetry = &ldapDBTableMapping.retrieveErrorRetry;

	/* Loop 'attempts' times of forever if -1 */
	for (count = retrieveRetry->attempts; (0 <= count) ||
	    (-1 == retrieveRetry->attempts); count --) {
		if (TRUE == singleReadFromDIT(map, domain, key, value, &res))
			/* It worked, return value irrelevant */
			return (0);

		if (LDAP_TIMEOUT == res) { /* Exceeded search timeout */
			value->dptr = NULL;
			return (0);
		}

		if (is_fatal_error(res))
			break;

		/*
		 * Didn't work. If not the special case where no repeats are
		 * done sleep.
		 */
		if (0 != retrieveRetry->attempts)
			(void) poll(NULL, 0, retrieveRetry->timeout*1000);
	}

	/* Make sure returned pointer is NULL */
	value->dptr = NULL;

	/* If we get here access failed work out what to return */
	if (ldapDBTableMapping.retrieveError == use_cached)
		return (0);

	return (res);
}

/*
 * FUNCTION:    write_to_dit();
 *
 * DESCRIPTION:	Maps and writes a NIS entry to the LDAP DIT.
 *		Also handles retry attempts, on failure, and interpretation of
 *		internal error codes.
 *
 * INPUTS:     	Pointer to (unqualified) map name
 *		Pointer to domain name
 *		The entries key
 *		What to write
 *		Replace flag indicating
 *			TRUE = Replace (overwrite) any existing entries
 *			FALSE = Return error if there are existing entries
 *		Flag indicating if we should tolerate mapping errors.
 *
 * OUTPUTS:   	SUCCESS = Write was successful
 *		FAILURE = Write failed
 *
 */
suc_code
write_to_dit(char *map, char *domain, datum key, datum value,
					bool_t replace, bool_t ignore_map_errs)
{
	int count;
	int res;
	__nisdb_retry_t	*storeRetry = &ldapDBTableMapping.storeErrorRetry;

	/* Initialize tsd */
	__nisdb_get_tsd()->domainContext = 0;
	__nisdb_get_tsd()->escapeFlag = '\0';

	for (count = 0; count < ypDomains.numDomains; count++) {
		if (0 == ypDomains.domainLabels[count])
			continue;
		if (0 == strcasecmp(domain, ypDomains.domainLabels[count])) {
			__nisdb_get_tsd()->domainContext =
			    ypDomains.domains[count];
			break;
		}
	}

	storeRetry = &ldapDBTableMapping.storeErrorRetry;

	/* Loop 'attempts' times of forever if -1 */
	for (count = storeRetry->attempts; (0 <= count) ||
	    (-1 == storeRetry->attempts); count --) {
		res = singleWriteToDIT(map, domain, &key, &value, replace);
		if (LDAP_SUCCESS == res)
			return (SUCCESS);

		if (is_fatal_error(res)) {
			/*
			 * The mapping failed and will fail again if it is
			 * retried. However there are some cases where an
			 * actual mapping fault (rather than a LDAP problem)
			 * may be ignored.
			 */
			if (ignore_map_errs) {
				switch (res) {
					case LDAP_INVALID_DN_SYNTAX:
					case LDAP_OBJECT_CLASS_VIOLATION:
					case LDAP_NOT_ALLOWED_ON_RDN:
					case MAP_NAMEFIELD_MATCH_ERROR:
					case MAP_NO_DN:
						return (SUCCESS);
					default:
						break;
				}
			}
			return (FAILURE);
		}

		if (ldapDBTableMapping.storeError != sto_retry)
			return (FAILURE);

		/*
		 * Didn't work. If not the special case where no repeats are
		 * done sleep.
		 */
		if (0 != storeRetry->attempts)
			(void) poll(NULL, 0, storeRetry->timeout*1000);

	}
	return (FAILURE);
}

/*
 * FUNCTION :	get_ttl_value()
 *
 * DESCRIPTION:	Get the TTL value, derived from mapping file or DIT, for a
 *		entry.
 *
 * GIVEN :	Pointer to map
 *		A flag indication if TTL should be max, min or random
 *
 * RETURNS :	TTL value in seconds.
 *		-1 on failure
 */
int
get_ttl_value(map_ctrl *map, TTL_TYPE type)
{
	__nis_table_mapping_t *table_map;
	int interval, res;
	char *myself = "get_ttl_value";

	/*  Get the mapping structure corresponding to `map.domain' */
	table_map = mappingFromMap(map->map_name, map->domain, &res);

	if (0 == table_map) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
		    "Get TTL request could not access map %s in domain %s "
		    "(error %d)", map->map_name, map->domain, res);
		return (-1);
	}

	switch (type) {
		case TTL_MAX:
			return (table_map->initTtlHi);

		case TTL_MIN:
			return (table_map->initTtlLo);

		default:
			logmsg(MSG_NOTIMECHECK, LOG_INFO,
			"%s passed illegal TTL type (%d)", myself, type);
			/* If unknown TTL type drop through to TTL_RAND */

		case TTL_RAND:
			interval = table_map->initTtlHi - table_map->initTtlLo;
			if (0 >= interval)
				return (table_map->initTtlLo);

			/*
			 * Must get a random value. We assume srand48() got
			 * called at initialization.
			 */
			return (lrand48() % interval);

		case TTL_RUNNING:
			return (table_map->ttl);


	}
}

/*
 * FUNCTION :	get_mapping_domain_list()
 *
 * DESCRIPTION:	Gets a list of domain names specified, by nisLDAPdomainContext
 *		attributes, in the mapping file. This is used only for initial
 *		DIT setup. Once the DIT has been set up get_domain_list() is
 *		used instead.
 *
 * GIVEN :	Pointer returned array.
 *
 * RETURNS :	Number of element in returned array.
 *		Array of elements this is in static memory
 *		and must not be freed by the caller.
 */
int
get_mapping_domain_list(char ***ptr)
{
	*ptr = ypDomains.domainLabels;
	return (ypDomains.numDomains);
}

/*
 * FUNCTION :	get_mapping_yppasswdd_domain_list()
 *
 * DESCRIPTION:	Gets a list of domain names specified, by the
 *		nisLDAPyppasswddDomains attribute, in the mapping file. This
 *		is the list of domains for which passwords should be changed.
 *
 * GIVEN :	Pointer returned array
 *
 * RETURNS :	Number of element in returned array.
 *		0 if no nisLDAPyppasswddDomains attribute is present.
 *		Array of elements this is in static memory
 *		and must not be freed by the caller.
 */
int
get_mapping_yppasswdd_domain_list(char ***ptr)
{
	*ptr = ypDomains.yppasswddDomainLabels;
	return (ypDomains.numYppasswdd);
}

/*
 * FUNCTION :	free_map_list()
 *
 * DESCRIPTION:	Frees a map list.
 *
 * GIVEN :	Pointer to the map list.
 *
 * RETURNS :	Nothing
 */
void
free_map_list(char **map_list)
{
	free_null_terminated_list(map_list);
}

/*
 * FUNCTION :	get_passwd_list()
 *
 * DESCRIPTION:	Gets a list of either passwd or passwd.adjunct map files
 *		defined in the mapping file. These are the files which have
 *		'magic' nisLDAPdatabaseIdMapping entries aliasing them to
 *		passwd or passwd.adjunct. This function is required so that
 *		yppasswdd can work out which maps to synchronize with any
 *		password changes.
 *
 *		This information is not currently stored by the parser but
 *		we can recover it from the hash table. This makes hard work but
 *		passwords should not be changed very frequently
 *
 * GIVEN :	Flag indicating if a list is required for passwd or
 *		passwd.adjunct
 *		Domain to return the list for.
 *
 * RETURNS :	Null terminated list of map names in malloced memory. To be
 *		freed by caller. (Possibly empty if no passwd maps found)
 *		NULL on error
 */
char **
get_passwd_list(bool_t adjunct, char *domain)
{
	char *myself = "get_passwd_list";
	__nis_hash_item_mt *it;
	int	i, size;
	char 	*end_ptr;
	char	*target;	/* What we are looking for */
	int	target_len;
	int	domain_len;
	char	**res;		/* Result array */
	char	**res_old;	/* Old value of res during realloc */
	int	array_size;	/* Current malloced size */
	int	res_count = 0;	/* Current result count */

	/*
	 * Always need an array even if just for terminator. Normally one
	 * chunk will be enough.
	 */
	res = am(myself, ARRAY_CHUNK * sizeof (char *));
	if (NULL == res)
		return (NULL);
	array_size = ARRAY_CHUNK;

	/* Set up target */
	if (adjunct)
		target = PASSWD_ADJUNCT_PREFIX;
	else
		target = PASSWD_PREFIX;
	target_len = strlen(target);
	domain_len = strlen(domain);

	/* Work out hash table length */
	size = sizeof (ldapMappingList.keys) / sizeof (ldapMappingList.keys[0]);
	/* For all hash table entries */
	for (i = 0; i < size; i++) {
		/* Walk linked list for this hash table entry */
		for (it = ldapMappingList.keys[i]; NULL != it; it = it->next) {
			/* Check right map */
			if ((target_len + domain_len + 1) > strlen(it->name))
				continue;
			if (0 != strncmp(it->name, target, target_len))
				continue;

			/* Check right domain (minus trailing dot) */
			if (strlen(domain) >= strlen(it->name))
				continue;
			end_ptr = it->name + strlen(it->name) -
			    strlen(domain) - 1;
			if (',' != *(end_ptr - 1))
				continue;
			if (0 != strncmp(end_ptr, domain, strlen(domain)))
				continue;

			/* Check if we need to enlarge array */
			if ((res_count + 1) >= array_size) {
				array_size += ARRAY_CHUNK;
				res_old = res;
				res = realloc(res, array_size *
				    sizeof (char *));
				if (NULL == res) {
					res_old[res_count] = NULL;
					free_passwd_list(res_old);
					return (NULL);
				}
			}

			/* What we really need is strndup() */
			res[res_count] = am(myself, end_ptr - it->name + 1);
			if (NULL == res[res_count]) {
				free_passwd_list(res);
				return (NULL);
			}

			/* Copy from start to end_ptr */
			(void) memcpy(res[res_count], it->name,
			    end_ptr-it->name - 1);
			res_count ++;
		}
	}

	/* Terminate array */
	res[res_count] = NULL;
	return (res);
}

/*
 * FUNCTION :	free_passwd_list()
 *
 * DESCRIPTION:	Frees a password list obtained with get_passwd_list()
 *
 * INPUTS :	Address of list to free.
 *
 * OUTPUTS :	Nothing
 */
void
free_passwd_list(char **list)
{
	free_null_terminated_list(list);
}

/*
 * FUNCTION :	free_null_terminated_list()
 *
 * DESCRIPTION:	Frees a generic null terminated list.
 *
 * INPUTS :	Address of list to free.
 *
 * OUTPUTS :	Nothing
 */
void
free_null_terminated_list(char **list)
{
	int index;

	/* Free all the strings */
	for (index = 0; NULL != list[index]; index ++)
		sfree(list[index]);

	/* Free the array */
	sfree(list);
}


/*
 * FUNCTION :	add_special_entries()
 *
 * DESCRIPTION:	Adds the special (YP_*) entries to a map.
 *
 *		Part of dit_access because requires access to the mapping
 *		file in order to work out if secure and interdomain entries
 *		should be created.
 *
 * GIVEN :	Pointer to an open, temporary, DBM file
 *		Pointer to map information (do not use DBM fields).
 *		Pointer to a location in which to return security flag
 *
 * RETURNS :	SUCCESS = All entries created
 *		FAILURE = Some entries not created
 */
suc_code
add_special_entries(DBM *db, map_ctrl *map, bool_t *secure_flag)
{
	char local_host[MAX_MASTER_NAME];
	__nis_table_mapping_t *table_map;
	int res;

	/* Last modified time is now */
	update_timestamp(db);

	/* Add domain name */
	addpair(db, yp_domain_name, map->domain);

	/* For N2L mode local machine is always the master */
	sysinfo(SI_HOSTNAME, local_host, sizeof (local_host));
	addpair(db, yp_master_name, local_host);

	/*  Get the mapping structure corresponding to `map.domain' */
	table_map = mappingFromMap(map->map_name, map->domain, &res);
	if (0 == table_map)
		return (FAILURE);

	/* Add secure and interdomain flags if required */
	if (table_map->securemap_flag) {
		addpair(db, yp_secure, "");
		*secure_flag = TRUE;
	} else {
		*secure_flag = FALSE;
	}
	if (table_map->usedns_flag)
		addpair(db, yp_interdomain, "");

	return (SUCCESS);
}

/*
 * FUNCTION:	update_map_from_dit()
 *
 * DESCRIPTION:	Core code called to update an entire map.
 *		Information is recovered from LDAP and used to build a duplicate
 *		copy of the live maps. When this is complete the maps are
 *		locked and then overwritten by the new copy.
 *
 * INPUTS:	map_ctrl containing lots of information about the map and a
 *		pointer to it's lock which will be required.
 *		Flag indicating if progress logging is required.
 *
 * OUTPUTS:	SUCCESS = Map updated
 *		FAILURE = Map not updated
 */
suc_code
update_map_from_dit(map_ctrl *map, bool_t log_flag) {
	__nis_table_mapping_t	*t;
	__nis_rule_value_t	*rv;
	__nis_ldap_search_t	*ls;
	__nis_object_dn_t	*objectDN = NULL;
	datum			*datval, *datkey;
	int			nr = 0, i, j, nv, numDNs;
	int			statP = SUCCESS, flag;
	char			*objname, **dn;
	/* Name of temporary entries DBM file */
	char			*temp_entries;
	/* Name of temporary TTL DBM file */
	char			*temp_ttl;
	/* Temporary DBM handles */
	DBM			*temp_entries_db;
	DBM			*temp_ttl_db;
	map_ctrl		temp_map;
	datum			key;
	char			*myself = "update_map_from_dit";
	bool_t			secure_flag;
	int			entry_count = 1;
	int			next_print = PRINT_FREQ;
	int			search_flag = SUCCESS;

	if (!map || !map->map_name || !map->domain) {
		return (FAILURE);
	}

	__nisdb_get_tsd()->escapeFlag = '\0';

	/*
	 * netgroup.byxxx maps are a special case. They are regenerated from
	 * the netgroup map, not the DIT, so handle special case.
	 */
	if ((0 == strcmp(map->map_name, NETGROUP_BYHOST)) ||
		0 == (strcmp(map->map_name,  NETGROUP_BYUSER))) {
		return (update_netgroup_byxxx(map));
	}

	/* Get the mapping information for the map */
	if ((t = mappingFromMap(map->map_name, map->domain, &statP)) == 0) {
		if (statP == MAP_NO_MAPPING_EXISTS)
			logmsg(MSG_NOTIMECHECK, LOG_WARNING,
			"%s: No mapping information available for %s,%s",
				myself, map->map_name, map->domain);
		return (FAILURE);
	}

	/* Allocate and set up names */
	if (SUCCESS != alloc_temp_names(map->map_path,
				&temp_entries, &temp_ttl)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
			"%s: Unable to create map names for %s",
			myself, map->map_path);
		return (FAILURE);
	}

	/* Create temp entry and TTL file */
	if ((temp_entries_db = dbm_open(temp_entries, O_RDWR | O_CREAT, 0644))
						== NULL) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s",
						myself, temp_entries);
		sfree(temp_entries);
		sfree(temp_ttl);
		return (FAILURE);
	}

	if ((temp_ttl_db = dbm_open(temp_ttl, O_RDWR | O_CREAT, 0644))
						== NULL) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s",
						myself, temp_ttl);
		dbm_close(temp_entries_db);
		delete_map(temp_entries);
		sfree(temp_entries);
		sfree(temp_ttl);
		return (FAILURE);
	}

	/* Initialize domainContext tsd */
	__nisdb_get_tsd()->domainContext = 0;
	for (i = 0; i < ypDomains.numDomains; i++) {
		if (0 == ypDomains.domainLabels[i])
			continue;
		if (0 == strcasecmp(map->domain, ypDomains.domainLabels[i])) {
			__nisdb_get_tsd()->domainContext = ypDomains.domains[i];
			break;
		}
	}

	if (!(objname = getFullMapName(map->map_name, map->domain))) {
		if (temp_entries_db)
			dbm_close(temp_entries_db);
		if (temp_ttl_db)
			dbm_close(temp_ttl_db);
		delete_map(temp_entries);
		sfree(temp_entries);
		delete_map(temp_ttl);
		sfree(temp_ttl);
		return (FAILURE);
	}

	/* Try each mapping for the map */
	for (flag = 0; t != 0 && search_flag != FAILURE; t = t->next) {

		/* Check if the mapping is the correct one */
		if (strcmp(objname, t->objName) != 0) {
			continue;
		}

		/* Check if rulesFromLDAP are provided */
		if (t->numRulesFromLDAP == 0) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"%s: No rulesFromLDAP available for %s (%s)",
				myself, t->dbId, map->map_name);
			continue;
		}

		/* Set flag to indicate update is enabled */
		flag = 1;
		/* Create ldap request for enumeration */
		for (objectDN = t->objectDN;
				objectDN && objectDN->read.base;
				objectDN = objectDN->next) {
			if ((ls = createLdapRequest(t, 0, 0, 1, NULL,
						objectDN)) == 0) {
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"%s: Failed to create "
					"ldapSearch request for "
					"%s (%s) for base %s",
					myself, t->dbId,
					map->map_name,
					objectDN->read.base);
				statP = FAILURE;
				search_flag = FAILURE;
				break;
			}

			if (log_flag) {
				printf("Waiting for LDAP search results.\n");
			}

			/* Query LDAP */
			nr = (ls->isDN)?0:-1;
			rv = ldapSearch(ls, &nr, 0, &statP);
			freeLdapSearch(ls);
			if (rv == 0) {
				if (statP == LDAP_NO_SUCH_OBJECT) {
				/*
				 * No Entry exists in the ldap server. Not
				 * a problem. Maybe there are just no entries
				 * in this map.
				 */
					continue;
				}
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"%s: ldapSearch error %d "
					"(%s) for %s (%s) for base %s",
					myself, statP, ldap_err2string(statP),
					t->dbId, map->map_name,
					objectDN->read.base);
				statP = FAILURE;
				search_flag = FAILURE;
				break;
			}

			if (log_flag) {
				printf("Processing search results.\n");
			}

			/* Obtain list of DNs for logging */
			if ((dn = findDNs(myself, rv, nr, 0, &numDNs)) == 0) {
				statP = FAILURE;
				search_flag = FAILURE;
				break;
			}

			/* For each entry in the result  do the following */
			for (i = 0; i < nr; i++) {
			/* Convert LDAP data to NIS equivalents */
				statP = buildNISRuleValue(t, &rv[i],
						map->domain);
				if (statP == MAP_INDEXLIST_ERROR)
					continue;
				if (statP != SUCCESS) {
					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
					    "%s: Conversion error %d (LDAP to "
					    "name=value pairs) "
					    "for (dn: %s) for "
					    "%s (%s) for base %s",
					    myself, statP, NIL(dn[i]),
					    t->dbId, map->map_name,
					    objectDN->read.base);
					continue;
				}

				/* Obtain the datum for value */
				datval = ruleValueToDatum(t, &rv[i], &statP);
				if (datval == 0) {
					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
						"%s: Conversion error %d "
						"(name=value pairs to NIS)"
						" for (dn: %s) for "
						"%s (%s) for base %s",
						myself, statP, NIL(dn[i]),
						t->dbId, map->map_name,
						objectDN->read.base);
					continue;
				}

				/* Obtain the datum for key */
				datkey = getKeyFromRuleValue(t, &rv[i],
						&nv, &statP);
				if (datkey == 0) {
					logmsg(MSG_NOTIMECHECK, LOG_WARNING,
						"%s: Unable to obtain NIS "
						"key from LDAP data (dn:%s) "
						"for %s (%s) for base %s",
						myself, NIL(dn[i]), t->dbId,
						map->map_name,
						objectDN->read.base);
					sfree(datval->dptr);
					sfree(datval);
					continue;
				}

				/* Write to the temporary map */
				for (j = 0; j < nv; j++, entry_count ++) {
					if (datkey[j].dsize == 0)
						continue;
					errno = 0;
					/* DBM_INSERT to match */
					/* singleReadFromDIT */
					if (dbm_store(temp_entries_db,
						datkey[j],
						*datval,
						DBM_INSERT) < 0) {
						/*
						 * For some cases errno may
						 * still be 0 but dbm_error
						 * isn't informative at all.
						 */
						logmsg(MSG_NOTIMECHECK,
						    LOG_WARNING,
						    "%s: dbm store error "
						    "(errno=%d) "
						    "for (key=%s, value=%s) "
						    "for %s (%s) for base %s",
						    myself,
						    errno,
						    datkey[j].dptr,
						    datval->dptr, t->dbId,
						    map->map_name,
						    objectDN->read.base);
						/* clear the error */
						dbm_clearerr(temp_entries_db);
					}
					sfree(datkey[j].dptr);

					if (log_flag && (entry_count >=
							next_print)) {
						printf("%d entries processed\n",
							entry_count);
						next_print *= 2;
					}

				}
				sfree(datkey);
				sfree(datval->dptr);
				sfree(datval);
			}

			freeRuleValue(rv, nr);
			freeDNs(dn, numDNs);
		} /* End of for over objectDN */
	}
	sfree(objname);

	if (t != 0 || flag == 0 || search_flag == FAILURE) {
		if (temp_entries_db)
			dbm_close(temp_entries_db);
		if (temp_ttl_db)
			dbm_close(temp_ttl_db);
		delete_map(temp_entries);
		sfree(temp_entries);
		delete_map(temp_ttl);
		sfree(temp_ttl);
		return (statP);
	}
	/* Set up enough of map_ctrl to call update_entry_ttl */
	temp_map.map_name = map->map_name;
	temp_map.domain = map->domain;
	temp_map.ttl = temp_ttl_db;

	/* Generate new TTL file */
	key = dbm_firstkey(temp_entries_db);
	while (key.dptr != 0) {
		if (!is_special_key(&key))
			/*
			 * We don't want all the entries to time out at the
			 * same time so create random TTLs.
			 */
			if (FAILURE == update_entry_ttl(&temp_map, &key,
								TTL_RAND))
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"%s: Could not update TTL for "
					"(key=%s) for map %s,%s",
					myself, NIL(key.dptr), map->map_name,
					map->domain);
		key = dbm_nextkey(temp_entries_db);
	}

	/* Update map TTL */
	if (SUCCESS != update_map_ttl(&temp_map)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not update map TTL "
			"for %s,%s", myself, map->map_name, map->domain);
	}

	/* Set up 'special' nis entries */
	add_special_entries(temp_entries_db, map, &secure_flag);

	/* Close temp DBM files */
	dbm_close(temp_entries_db);
	dbm_close(temp_ttl_db);

	/* Lock access to the map for copy */
	lock_map_ctrl(map);

	/* Move temp maps to real ones */
	rename_map(temp_entries, map->map_path, secure_flag);
	rename_map(temp_ttl, map->ttl_path, secure_flag);

	/* Free file names */
	sfree(temp_entries);
	sfree(temp_ttl);

	/* Unlock map */
	unlock_map_ctrl(map);

	return (SUCCESS);
}

/*
 * FUNCTION :	get_mapping_map_list()
 *
 * DESCRIPTION:	Gets a list of nis maps for a given domain specified in the
 *		mapping file. This information is not saved so have to go
 *		through the entire hash table. At least this is only done at
 *		initialization time.
 *
 * GIVEN :	Domain name
 *
 * RETURNS :	List of map names in malloced memory. MUST BE FREED BY CALLER.
 */
char **
get_mapping_map_list(char *domain)
{
	char *myself = "get_mapping_map_list";
	__nis_hash_item_mt *it;
	int	i, j, size;
	char 	*end_ptr;
	char	**res;		/* Result array */
	char	**res_old;	/* Old value of res during realloc */
	int	array_size;	/* Current malloced size */
	int	res_count = 0;	/* Current result count */

	/*
	 * Always need an array even if just for terminator. Normally one
	 * chunk will be enough.
	 */
	res = am(myself, ARRAY_CHUNK * sizeof (char *));
	if (NULL == res)
		return (NULL);
	array_size = ARRAY_CHUNK;

	/* Work out hash table length */
	size = sizeof (ldapMappingList.keys) / sizeof (ldapMappingList.keys[0]);
	/* For all hash table entries */
	for (i = 0; i < size; i++) {
		/* Walk linked list for this hash table entry */
		for (it = ldapMappingList.keys[i]; NULL != it; it = it->next) {

			/* Check it's not a split field entry */
			if (0 != ((__nis_table_mapping_t *)it)->numSplits)
				continue;

			/* Check right domain (minus trailing dot) */
			if (strlen(domain) >= strlen(it->name))
				continue;
			end_ptr = it->name + strlen(it->name) -
			    strlen(domain) - 1;
			if (',' != *(end_ptr - 1))
				continue;
			if (0 != strncmp(end_ptr, domain, strlen(domain)))
				continue;

			/* Check if we need to enlarge array */
			if ((res_count + 1) >= array_size) {
				array_size += ARRAY_CHUNK;
				res_old = res;
				res = realloc(res, array_size *
				    sizeof (char *));
				if (NULL == res) {
					res_old[res_count] = NULL;
					free_passwd_list(res_old);
					return (NULL);
				}
			}

			/*
			 * We will need the sequence number when we come to
			 * sort the entries so for now store a pointer to
			 * the __nis_hash_item_mt.
			 */
			res[res_count] = (char *)it;
			res_count ++;
		}
	}

	/* Terminate array */
	res[res_count] = NULL;

	/* Bubble sort entries into the same order as mapping file */
	for (i = res_count - 2; 0 <= i; i--) {
		for (j = 0; j <= i; j++) {
			if (((__nis_table_mapping_t *)res[j + 1])->seq_num <
			    ((__nis_table_mapping_t *)res[j])->seq_num) {
				end_ptr = res[j];
				res[j] = res[j+1];
				res[j + 1] = end_ptr;
			}
		}
	}

	/* Finally copy the real strings in to each entry */
	for (i = 0; NULL != res[i]; i ++) {

		/* Get hash table entry back */
		it = (__nis_hash_item_mt *)res[i];

		end_ptr = it->name + strlen(it->name) - strlen(domain) - 1;

		/* What we really need is strndup() */
		res[i] = am(myself, end_ptr - it->name + 1);
		if (NULL == res[i]) {
			free_map_list(res);
			return (NULL);
		}

		/* Copy from start to end_ptr */
		(void) memcpy(res[i], it->name, end_ptr-it->name - 1);
	}

	return (res);
}

/*
 * FUNCTION :	make_nis_container()
 *
 * DESCRIPTION: Sets up container for map_name in the DIT.
 *
 * GIVEN :	Map name
 *		The domain name.
 *		Flag indicating if container should be created.
 *
 * RETURNS :	SUCCESS	= It worked
 *		FAILURE	= There was a problem.
 */
suc_code
make_nis_container(char *map_name, char *domain, bool_t init_containers) {
	int			i, rc, statP = SUCCESS;
	__nis_table_mapping_t	*t;
	char			*dn;
	char			*myself = "make_nis_container";

	if (!map_name || !domain)
		return (FAILURE);

	if (FALSE == init_containers) {
		/*
		 * If we are not creating containers it is debatable what we
		 * should do . Maybe we should check for a pre-
		 * existing container and return failure if it does not exist.
		 *
		 * For now we assume the user will not have called us in this
		 * mode unless they know what they are doing. So return
		 * success. If they have got it wrong then latter writes will
		 * fail.
		 */
		return (SUCCESS);
	}

	/* Get the mapping information for the map */
	if ((t = mappingFromMap(map_name, domain, &statP)) == 0) {
		if (statP == MAP_NO_MAPPING_EXISTS)
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
			"%s: No mapping information available for %s,%s",
				myself, NIL(map_name), NIL(domain));
		return (FAILURE);
	}

	/* Two times. One for readDN and other for writeDN */
	for (i = 0; i < 2; i++) {
		if (i == 0)
			dn = t->objectDN->read.base;
		else {
			if (t->objectDN->write.base == 0) {
				logmsg(MSG_NOTIMECHECK, LOG_INFO,
					"%s: No baseDN in writespec. Write "
					"disabled for %s,%s",
					myself, map_name, domain);
				break;
			}
			if (!strcasecmp(dn, t->objectDN->write.base))
				break;
			dn = t->objectDN->write.base;
		}

		if ((rc = makeNISObject(0, dn)) == FAILURE) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"%s: Unable to create ldap container (dn: %s) "
				"for %s,%s", myself, dn, map_name, domain);
			return (FAILURE);
		}
	}
	return (SUCCESS);
}

/*
 * FUNCTION :	make_nis_domain()
 *
 * DESCRIPTION:	Sets up a nisDomainObject in the DIT
 *
 * GIVEN:	Name of the domain
 *		Flag indicating if domain should be create or possibly just
 *		checked for.
 */
suc_code
make_nis_domain(char *domain, bool_t init_containers) {

	if (FALSE == init_containers) {
		/*
		 * If we are not creating containers it is debatable what we
		 * should do with domains. Maybe we should check for a pre-
		 * existing domain and return failure if it does not exist.
		 *
		 * For now we assume the user will not have called us in this
		 * mode unless they know what they are doing. So return
		 * success. If they have got it wrong then latter writes will
		 * fail.
		 */
		return (SUCCESS);
	}

	/* Create the domain */
	return (makeNISObject(domain, 0));
}

/*
 * FUNCTION:	update_netgroup_byxxx()
 *
 * DESCRIPTION:	Updates the netgroup.byxxx series of maps based on the current
 *		netgroup file. We invoke revnetgroup so that if any changes
 *		are made to this utility the same changes are made here.
 *
 * INPUTS:	map_ctrl containing lots of information about the map and a
 *		pointer to it's lock which will be required.
 *
 * OUTPUTS:	SUCCESS = Map updated
 *		FAILURE = Map not updated
 */
suc_code
update_netgroup_byxxx(map_ctrl *map) {
	/* Name of temporary entries DBM file */
	char			*temp_entries;
	/* Name of temporary TTL DBM file */
	char			*temp_ttl;
	/* Temporary DBM handles */
	DBM			*temp_entries_db;
	DBM			*temp_ttl_db;
	map_ctrl		temp_map;
	char			*myself = "update_netgroup_byxxx";
	char			*cmdbuf;
	int			cmd_length;
	datum			key;
	map_ctrl		*netgroupmap;
	int			res;
	/* Temporary revnetgroup files */
	const char 		*byhost = NETGROUP_BYHOST "_REV" TEMP_POSTFIX;
	const char 		*byuser = NETGROUP_BYUSER "_REV" TEMP_POSTFIX;
	const char		*temp_file_name;


	/*
	 * We need to use two different temporary files: one for netgroup.byhost
	 * and other for netgroup.byuser, since these two maps can be updated
	 * simultaneously. These temporary files will hold the output of
	 * revnetgroup [-h|-u] command. They are then used to generate the
	 * corresponding dbm files and thereafter deleted.
	 */
	if (0 == strcmp(map->map_name, NETGROUP_BYHOST))
		temp_file_name = byhost;
	else
		temp_file_name = byuser;

	/* Alloc enough cmd buf for revnet cmd */
	cmd_length = strlen("/usr/sbin/makedbm -u ") +
			(strlen(map->map_path) - strlen(map->map_name)) +
			strlen(NETGROUP_MAP) +
			strlen(" | /usr/sbin/revnetgroup -h > ") +
			(strlen(map->map_path) - strlen(map->map_name)) +
			strlen(temp_file_name) + 1;
	cmdbuf = am(myself, cmd_length);

	if (NULL == cmdbuf) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"%s: Could not alloc cmdbuf.", myself);
		return (FAILURE);
	}

	/*
	 * If necessary update (and wait for) netgroups map. This is a lot of
	 * work but if the netgroup map itself is not being accessed it may
	 * contain information that is not up to date with the DIT.
	 *
	 * We use the cmdbuf to store the qualified netgroup map name there will
	 * be enough space for this but we are not yet creating the cmd.
	 */
	strlcpy(cmdbuf, map->map_path, strlen(map->map_path) -
						strlen(map->map_name) + 1);
	strcat(cmdbuf, NETGROUP_MAP);
	netgroupmap = (map_ctrl *)shim_dbm_open(cmdbuf,
						O_RDWR | O_CREAT, 0644);
	if (NULL == netgroupmap) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"%s: Could not update %s.", myself, cmdbuf);
		sfree(cmdbuf);
		return (FAILURE);
	}

	if (has_map_expired(netgroupmap)) {
		lock_map_ctrl(netgroupmap);
		update_map_if_required(netgroupmap, TRUE);
		unlock_map_ctrl(netgroupmap);
	}
	shim_dbm_close((DBM *)netgroupmap);

	/* Dump netgroup file through revnetgroup to a temp file */
	strcpy(cmdbuf, "/usr/sbin/makedbm -u ");

	/* Unmake the netgroup file in same domain as map */
	strncat(cmdbuf, map->map_path, strlen(map->map_path) -
						strlen(map->map_name));
	strcat(cmdbuf, NETGROUP_MAP);

	if (0 == strcmp(map->map_name, NETGROUP_BYHOST)) {
		strcat(cmdbuf, " | /usr/sbin/revnetgroup -h > ");
	} else {
		strcat(cmdbuf, " | /usr/sbin/revnetgroup -u > ");
	}

	/* Create temp file file in same domain as map */
	strncat(cmdbuf, map->map_path, strlen(map->map_path) -
						strlen(map->map_name));
	strcat(cmdbuf, temp_file_name);

	if (0 > system(cmdbuf)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not run \"%s\" "
			"(errno=%d)", myself, cmdbuf, errno);
		sfree(cmdbuf);
		return (FAILURE);
	}
	sfree(cmdbuf);

	/* Allocate and set up names */
	if (SUCCESS != alloc_temp_names(map->map_path,
				&temp_entries, &temp_ttl)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
			"%s: Unable to create map names for %s",
			myself, map->map_path);
		return (FAILURE);
	}

	/* Make the temporary DBM file */
	cmdbuf = am(myself, strlen("/usr/sbin/makedbm") +
			(strlen(map->map_path) - strlen(map->map_name)) +
			strlen(temp_file_name) +
			strlen(temp_entries) + 3);
	if (NULL == cmdbuf) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"%s: Could not allocate cmdbuf.", myself);
		sfree(temp_entries);
		sfree(temp_ttl);
		return (FAILURE);
	}

	strcpy(cmdbuf, "/usr/sbin/makedbm ");
	strncat(cmdbuf, map->map_path, strlen(map->map_path) -
						strlen(map->map_name));
	strcat(cmdbuf, temp_file_name);
	strcat(cmdbuf, " ");
	strcat(cmdbuf, temp_entries);

	if (0 > system(cmdbuf)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not run \"%s\" "
			"(errno=%d)", myself, cmdbuf, errno);
		sfree(cmdbuf);
		sfree(temp_entries);
		sfree(temp_ttl);
		return (FAILURE);
	}

	/* Already have enough command buffer to rm temporary file */
	strlcpy(cmdbuf, map->map_path, strlen(map->map_path) -
						strlen(map->map_name) + 1);
	strcat(cmdbuf, temp_file_name);
	res = unlink(cmdbuf);
	/* If the temp file did not exist no problem. Probably had no entries */
	if ((0 != res) && (ENOENT != errno)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not delete \"%s\" "
			"(errno=%d)", myself, cmdbuf, errno);
		sfree(temp_entries);
		sfree(temp_ttl);
		sfree(cmdbuf);
		return (FAILURE);
	}
	sfree(cmdbuf);

	if ((temp_entries_db = dbm_open(temp_entries, O_RDWR | O_CREAT, 0644))
						== NULL) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s",
						myself, temp_entries);
		sfree(temp_entries);
		sfree(temp_ttl);
		return (FAILURE);
	}

	if ((temp_ttl_db = dbm_open(temp_ttl, O_RDWR | O_CREAT, 0644))
						== NULL) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s",
						myself, temp_ttl);
		dbm_close(temp_entries_db);
		sfree(temp_entries);
		sfree(temp_ttl);
		return (FAILURE);
	}

	/*
	 * Set up enough of map_ctrl to call update_entry_ttl. Since there is
	 * no mapping, and thus not TTL, defined for these maps use the TTL
	 * values for netgroup map
	 */
	temp_map.map_name = NETGROUP_MAP;
	temp_map.domain = map->domain;
	temp_map.ttl = temp_ttl_db;

	/*
	 * Generate new TTL file.  Since these maps work only on the whole map
	 * expiry these will not actually be used but there presence makes it
	 * easier to handle these maps in the same way as other maps.
	 */
	key = dbm_firstkey(temp_entries_db);
	while (key.dptr != 0) {
		if (!is_special_key(&key))
			/*
			 * For these maps want all timouts to be maximum
			 */
			if (FAILURE == update_entry_ttl(&temp_map, &key,
								TTL_MAX))
				logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"%s: Could not update TTL for "
					"(key=%s) for map %s,%s",
					myself, NIL(key.dptr), map->map_name,
					map->domain);
		key = dbm_nextkey(temp_entries_db);
	}

	/* Update map TTL */
	update_map_ttl(&temp_map);

	/* Close temp DBM files */
	dbm_close(temp_entries_db);
	dbm_close(temp_ttl_db);

	/* Lock access to the map for copy */
	lock_map_ctrl(map);

	/* Move temp maps to real ones */
	rename_map(temp_entries, map->map_path, FALSE);
	rename_map(temp_ttl, map->ttl_path, FALSE);

	/* Free file names */
	sfree(temp_entries);
	sfree(temp_ttl);

	/* Unlock map */
	unlock_map_ctrl(map);

	return (SUCCESS);
}