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

#include <assert.h>
#include <errno.h>
#include <memory.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libintl.h>
#include <syslog.h>
#include <sys/door.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <synch.h>
#include <pthread.h>
#include <unistd.h>
#include <lber.h>
#include <ldap.h>
#include <ctype.h>	/* tolower */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ucred.h>
#include "cachemgr.h"
#include "solaris-priv.h"
#include "ns_connmgmt.h"

static rwlock_t	ldap_lock = DEFAULTRWLOCK;
static int	sighup_update = FALSE;
extern admin_t	current_admin;

extern int is_root_or_all_privs(char *dc_str, ucred_t **ucp);

/* variables used for SIGHUP wakeup on sleep */
static mutex_t			sighuplock;
static cond_t			cond;

/* refresh time statistics */
static time_t	prev_refresh_time = 0;

/* variables used for signaling parent process */
static mutex_t	sig_mutex;
static int	signal_done = FALSE;

/* TCP connection timeout (in milliseconds) */
static int tcptimeout = NS_DEFAULT_BIND_TIMEOUT * 1000;

#ifdef SLP
extern int	use_slp;
#endif /* SLP */

/* nis domain information */
#define	_NIS_FILTER		"objectclass=nisDomainObject"
#define	_NIS_DOMAIN		"nisdomain"

#define	CACHESLEEPTIME		600
/*
 * server list refresh delay when in "no server" mode
 * (1 second)
 */
#define	REFRESH_DELAY_WHEN_NO_SERVER	1

typedef enum {
	INFO_OP_CREATE		= 0,
	INFO_OP_DELETE		= 1,
	INFO_OP_REFRESH		= 2,
	INFO_OP_REFRESH_WAIT	= 3,
	INFO_OP_GETSERVER	= 4,
	INFO_OP_GETSTAT		= 5,
	INFO_OP_REMOVESERVER	= 6
} info_op_t;

typedef enum {
	INFO_RW_UNKNOWN		= 0,
	INFO_RW_READONLY	= 1,
	INFO_RW_WRITEABLE	= 2
} info_rw_t;

typedef enum {
	INFO_SERVER_JUST_INITED	= -1,
	INFO_SERVER_UNKNOWN	= 0,
	INFO_SERVER_CONNECTING	= 1,
	INFO_SERVER_UP		= 2,
	INFO_SERVER_ERROR 	= 3,
	INFO_SERVER_REMOVED	= 4
} info_server_t;

typedef enum {
	INFO_STATUS_UNKNOWN	= 0,
	INFO_STATUS_ERROR 	= 1,
	INFO_STATUS_NEW   	= 2,
	INFO_STATUS_OLD		= 3
} info_status_t;

typedef enum {
	CACHE_OP_CREATE		= 0,
	CACHE_OP_DELETE		= 1,
	CACHE_OP_FIND		= 2,
	CACHE_OP_ADD		= 3,
	CACHE_OP_GETSTAT	= 4
} cache_op_t;

typedef enum {
	CACHE_MAP_UNKNOWN	= 0,
	CACHE_MAP_DN2DOMAIN	= 1
} cache_type_t;

typedef struct server_info_ext {
	char			*addr;
	char			*hostname;
	char			*rootDSE_data;
	char			*errormsg;
	info_rw_t		type;
	info_server_t		server_status;
	info_server_t		prev_server_status;
	info_status_t 		info_status;
	ns_server_status_t	change;
} server_info_ext_t;

typedef struct server_info {
	struct server_info 	*next;
	mutex_t			mutex[2];	/* 0: current copy lock */
						/* 1: update copy lock */
	server_info_ext_t	sinfo[2]; /* 0: current, 1:  update copy */
} server_info_t;

typedef struct cache_hash {
	cache_type_t		type;
	char			*from;
	char			*to;
	struct cache_hash	*next;
} cache_hash_t;

/*
 * The status of a server to be removed. It can be up or down.
 */
typedef struct rm_svr {
	char	*addr;
	int	up; /* 1: up, 0: down */
} rm_svr_t;

static int getldap_destroy_serverInfo(server_info_t *head);
static void test_server_change(server_info_t *head);
static void remove_server(char *addr);
static ns_server_status_t set_server_status(char *input, server_info_t *head);
static void create_buf_and_notify(char *input, ns_server_status_t st);

/*
 * Load configuration
 * The code was in signal handler getldap_revalidate
 * It's moved out of the handler because it could cause deadlock
 * return: 1 SUCCESS
 *         0 FAIL
 */
static int
load_config() {
	ns_ldap_error_t *error;
	int		rc = 1;

	(void) __ns_ldap_setServer(TRUE);

	(void) rw_wrlock(&ldap_lock);
	if ((error = __ns_ldap_LoadConfiguration()) != NULL) {
		logit("Error: Unable to read '%s': %s\n",
			NSCONFIGFILE, error->message);
		__ns_ldap_freeError(&error);
		rc = 0; /* FAIL */
	} else
		sighup_update = TRUE;

	(void) rw_unlock(&ldap_lock);

	return (rc);
}

/*
 * Calculate a hash for a string
 * Based on elf_hash algorithm, hash is case insensitive
 * Uses tolower instead of _tolower because of I18N
 */

static unsigned long
getldap_hash(const char *str)
{
	unsigned int	hval = 0;

	while (*str) {
		unsigned int	g;

		hval = (hval << 4) + tolower(*str++);
		if ((g = (hval & 0xf0000000)) != 0)
			hval ^= g >> 24;
		hval &= ~g;
	}
	return ((unsigned long)hval);
}

/*
 * Remove a hash table entry.
 * This function expects a lock in place when called.
 */

static cache_hash_t *
getldap_free_hash(cache_hash_t *p)
{
	cache_hash_t	*next;

	p->type = CACHE_MAP_UNKNOWN;
	if (p->from)
		free(p->from);
	if (p->to)
		free(p->to);
	next = p->next;
	p->next = NULL;
	free(p);
	return (next);
}

/*
 * Scan a hash table hit for a matching hash entry.
 * This function expects a lock in place when called.
 */
static cache_hash_t *
getldap_scan_hash(cache_type_t type, char *from,
		cache_hash_t *idx)
{
	while (idx) {
		if (idx->type == type &&
		    strcasecmp(from, idx->from) == 0) {
			return (idx);
		}
		idx = idx->next;
	}
	return ((cache_hash_t *)NULL);
}

/*
 * Format and return the cache data statistics
 */
static int
getldap_get_cacheData_stat(int max, int current, char **output)
{
#define	C_HEADER0	"Cache data information: "
#define	C_HEADER1	"  Maximum cache entries:   "
#define	C_HEADER2	"  Number of cache entries: "
	int		hdr0_len = strlen(gettext(C_HEADER0));
	int		hdr1_len = strlen(gettext(C_HEADER1));
	int		hdr2_len = strlen(gettext(C_HEADER2));
	int		len;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_get_cacheData_stat()...\n");
	}

	*output = NULL;

	len = hdr0_len + hdr1_len + hdr2_len +
	    3 * strlen(DOORLINESEP) + 21;
	*output = malloc(len);
	if (*output == NULL)
		return (-1);

	(void) snprintf(*output, len, "%s%s%s%10d%s%s%10d%s",
	    gettext(C_HEADER0), DOORLINESEP,
	    gettext(C_HEADER1), max, DOORLINESEP,
	    gettext(C_HEADER2), current, DOORLINESEP);

	return (NS_LDAP_SUCCESS);
}

static int
getldap_cache_op(cache_op_t op, cache_type_t type,
			char *from, char **to)
{
#define	CACHE_HASH_MAX		257
#define	CACHE_HASH_MAX_ENTRY	256
	static cache_hash_t	*hashTbl[CACHE_HASH_MAX];
	cache_hash_t		*next, *idx, *newp;
	unsigned long		hash;
	static rwlock_t 	cache_lock = DEFAULTRWLOCK;
	int 			i;
	static int		entry_num = 0;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_cache_op()...\n");
	}
	switch (op) {
	case CACHE_OP_CREATE:
		if (current_admin.debug_level >= DBG_ALL) {
			logit("operation is CACHE_OP_CREATE...\n");
		}
		(void) rw_wrlock(&cache_lock);

		for (i = 0; i < CACHE_HASH_MAX; i++) {
			hashTbl[i] = NULL;
		}
		entry_num = 0;

		(void) rw_unlock(&cache_lock);
		break;

	case CACHE_OP_DELETE:
		if (current_admin.debug_level >= DBG_ALL) {
			logit("operation is CACHE_OP_DELETE...\n");
		}
		(void) rw_wrlock(&cache_lock);

		for (i = 0; i < CACHE_HASH_MAX; i++) {
			next = hashTbl[i];
			while (next != NULL) {
				next = getldap_free_hash(next);
			}
			hashTbl[i] = NULL;
		}
		entry_num = 0;

		(void) rw_unlock(&cache_lock);
		break;

	case CACHE_OP_ADD:
		if (current_admin.debug_level >= DBG_ALL) {
			logit("operation is CACHE_OP_ADD...\n");
		}
		if (from == NULL || to == NULL || *to == NULL)
			return (-1);
		hash = getldap_hash(from) % CACHE_HASH_MAX;
		(void) rw_wrlock(&cache_lock);
		idx = hashTbl[hash];
		/*
		 * replace old "to" value with new one
		 * if an entry with same "from"
		 * already exists
		 */
		if (idx) {
			newp = getldap_scan_hash(type, from, idx);
			if (newp) {
				free(newp->to);
				newp->to = strdup(*to);
				(void) rw_unlock(&cache_lock);
				return (NS_LDAP_SUCCESS);
			}
		}

		if (entry_num > CACHE_HASH_MAX_ENTRY) {
			(void) rw_unlock(&cache_lock);
			return (-1);
		}

		newp = (cache_hash_t *)malloc(sizeof (cache_hash_t));
		if (newp == NULL) {
			(void) rw_unlock(&cache_lock);
			return (NS_LDAP_MEMORY);
		}
		newp->type = type;
		newp->from = strdup(from);
		newp->to = strdup(*to);
		newp->next = idx;
		hashTbl[hash] = newp;
		entry_num++;
		(void) rw_unlock(&cache_lock);
		break;

	case CACHE_OP_FIND:
		if (current_admin.debug_level >= DBG_ALL) {
			logit("operation is CACHE_OP_FIND...\n");
		}
		if (from == NULL || to == NULL)
			return (-1);
		*to = NULL;
		hash = getldap_hash(from) % CACHE_HASH_MAX;
		(void) rw_rdlock(&cache_lock);
		idx = hashTbl[hash];
		idx = getldap_scan_hash(type, from, idx);
		if (idx)
			*to = strdup(idx->to);
		(void) rw_unlock(&cache_lock);
		if (idx == NULL)
			return (-1);
		break;

	case CACHE_OP_GETSTAT:
		if (current_admin.debug_level >= DBG_ALL) {
			logit("operation is CACHE_OP_GETSTAT...\n");
		}
		if (to == NULL)
			return (-1);

		return (getldap_get_cacheData_stat(CACHE_HASH_MAX_ENTRY,
		    entry_num, to));
		break;

	default:
		logit("getldap_cache_op(): "
		    "invalid operation code (%d).\n", op);
		return (-1);
		break;
	}
	return (NS_LDAP_SUCCESS);
}
/*
 * Function: sync_current_with_update_copy
 *
 * This function syncs up the 2 sinfo copies in info.
 *
 * The 2 copies are identical most of time.
 * The update copy(sinfo[1]) could be different when
 * getldap_serverInfo_refresh thread is refreshing the server list
 * and calls getldap_get_rootDSE to update info.  getldap_get_rootDSE
 * calls sync_current_with_update_copy to sync up 2 copies before thr_exit.
 * The calling sequence is
 *  getldap_serverInfo_refresh->
 *  getldap_get_serverInfo_op(INFO_OP_CREATE,...)->
 *  getldap_set_serverInfo->
 *  getldap_get_rootDSE
 *
 * The original server_info_t has one copy of server info. When libsldap
 * makes door call GETLDAPSERVER to get the server info and getldap_get_rootDSE
 * is updating the server info, it would hit a unprotected window in
 * getldap_rootDSE. The door call  will not get server info and libsldap
 * fails at making ldap connection.
 *
 * The new server_info_t provides GETLDAPSERVER thread with a current
 * copy(sinfo[0]). getldap_get_rootDSE only works on the update copy(sinfo[1])
 * and syncs up 2 copies before thr_exit. This will close the window in
 * getldap_get_rootDSE.
 *
 */
static void
sync_current_with_update_copy(server_info_t *info)
{
	if (current_admin.debug_level >= DBG_ALL) {
		logit("sync_current_with_update_copy()...\n");
	}

	(void) mutex_lock(&info->mutex[1]);
	(void) mutex_lock(&info->mutex[0]);

	if (info->sinfo[1].server_status == INFO_SERVER_UP &&
	    info->sinfo[0].server_status != INFO_SERVER_UP)
		info->sinfo[1].change = NS_SERVER_UP;
	else if (info->sinfo[1].server_status != INFO_SERVER_UP &&
	    info->sinfo[0].server_status == INFO_SERVER_UP)
		info->sinfo[1].change = NS_SERVER_DOWN;
	else
		info->sinfo[1].change = 0;


	/* free memory in current copy first */
	if (info->sinfo[0].addr)
		free(info->sinfo[0].addr);
	info->sinfo[0].addr = NULL;

	if (info->sinfo[0].hostname)
		free(info->sinfo[0].hostname);
	info->sinfo[0].hostname = NULL;

	if (info->sinfo[0].rootDSE_data)
		free(info->sinfo[0].rootDSE_data);
	info->sinfo[0].rootDSE_data = NULL;

	if (info->sinfo[0].errormsg)
		free(info->sinfo[0].errormsg);
	info->sinfo[0].errormsg = NULL;

	/*
	 * make current and update copy identical
	 */
	info->sinfo[0] = info->sinfo[1];

	/*
	 * getldap_get_server_stat() reads the update copy sinfo[1]
	 * so it can't be freed or nullified yet at this point.
	 *
	 * The sinfo[0] and sinfo[1] have identical string pointers.
	 * strdup the strings to avoid the double free problem.
	 * The strings of sinfo[1] are freed in
	 * getldap_get_rootDSE() and the strings of sinfo[0]
	 * are freed earlier in this function. If the pointers are the
	 * same, they will be freed twice.
	 */
	if (info->sinfo[1].addr)
		info->sinfo[0].addr = strdup(info->sinfo[1].addr);
	if (info->sinfo[1].hostname)
		info->sinfo[0].hostname = strdup(info->sinfo[1].hostname);
	if (info->sinfo[1].rootDSE_data)
		info->sinfo[0].rootDSE_data =
		    strdup(info->sinfo[1].rootDSE_data);
	if (info->sinfo[1].errormsg)
		info->sinfo[0].errormsg = strdup(info->sinfo[1].errormsg);

	(void) mutex_unlock(&info->mutex[0]);
	(void) mutex_unlock(&info->mutex[1]);

}

static void *
getldap_get_rootDSE(void *arg)
{
	server_info_t	*serverInfo = (server_info_t *)arg;
	char		*rootDSE;
	int		exitrc = NS_LDAP_SUCCESS;
	pid_t		ppid;
	int		server_found = 0;

	ns_ldap_error_t *error = NULL;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_get_rootDSE()....\n");
	}

	/* initialize the server info element */
	(void) mutex_lock(&serverInfo->mutex[1]);
	serverInfo->sinfo[1].type	= INFO_RW_UNKNOWN;
	serverInfo->sinfo[1].info_status =
	    INFO_STATUS_UNKNOWN;
	/*
	 * When the sever list is refreshed over and over,
	 * this function is called each time it is refreshed.
	 * The previous server status of the update copy(sinfo[1])
	 * is the status of the current copy
	 */
	(void) mutex_lock(&serverInfo->mutex[0]);
	serverInfo->sinfo[1].prev_server_status =
	    serverInfo->sinfo[0].server_status;
	(void) mutex_unlock(&serverInfo->mutex[0]);

	serverInfo->sinfo[1].server_status =
	    INFO_SERVER_UNKNOWN;
	if (serverInfo->sinfo[1].rootDSE_data)
		free(serverInfo->sinfo[1].rootDSE_data);
	serverInfo->sinfo[1].rootDSE_data	= NULL;
	if (serverInfo->sinfo[1].errormsg)
		free(serverInfo->sinfo[1].errormsg);
	serverInfo->sinfo[1].errormsg 		= NULL;
	(void) mutex_unlock(&serverInfo->mutex[1]);

	(void) mutex_lock(&serverInfo->mutex[1]);
	serverInfo->sinfo[1].server_status = INFO_SERVER_CONNECTING;
	(void) mutex_unlock(&serverInfo->mutex[1]);

	/*
	 * WARNING: anon_fallback == 1 (last argument) means that when
	 * __ns_ldap_getRootDSE is unable to bind using the configured
	 * credentials, it will try to fall back to using anonymous, non-SSL
	 * mode of operation.
	 *
	 * This is for backward compatibility reasons - we might have machines
	 * in the field with broken configuration (invalid credentials) and we
	 * don't want them to be disturbed.
	 */
	if (__ns_ldap_getRootDSE(serverInfo->sinfo[1].addr,
	    &rootDSE,
	    &error,
	    SA_ALLOW_FALLBACK) != NS_LDAP_SUCCESS) {
		(void) mutex_lock(&serverInfo->mutex[1]);
		serverInfo->sinfo[1].server_status = INFO_SERVER_ERROR;
		serverInfo->sinfo[1].info_status = INFO_STATUS_ERROR;
		serverInfo->sinfo[1].errormsg =	strdup(error->message);

		if (error != NULL) {
			(void) __ns_ldap_freeError(&error);
		}

		if (current_admin.debug_level >= DBG_ALL) {
			logit("getldap_get_rootDSE: %s.\n",
			    serverInfo->sinfo[1].errormsg);
		}
		(void) mutex_unlock(&serverInfo->mutex[1]);
		/*
		 * sync sinfo copies in the serverInfo.
		 * protected by mutex
		 */
		sync_current_with_update_copy(serverInfo);
		thr_exit((void *) -1);
	}

	(void) mutex_lock(&serverInfo->mutex[1]);

	/* assume writeable, i.e., can do modify */
	serverInfo->sinfo[1].type		= INFO_RW_WRITEABLE;
	serverInfo->sinfo[1].server_status	= INFO_SERVER_UP;
	serverInfo->sinfo[1].info_status	= INFO_STATUS_NEW;
	/* remove the last DOORLINESEP */
	*(rootDSE+strlen(rootDSE)-1) = '\0';
	serverInfo->sinfo[1].rootDSE_data = rootDSE;

	server_found = 1;

	(void) mutex_unlock(&serverInfo->mutex[1]);

	/*
	 * sync sinfo copies in the serverInfo.
	 * protected by mutex
	 */
	sync_current_with_update_copy(serverInfo);
	/*
	 * signal that the ldap_cachemgr parent process
	 * should exit now, if it is still waiting
	 */
	(void) mutex_lock(&sig_mutex);
	if (signal_done == FALSE && server_found) {
		ppid = getppid();
		(void) kill(ppid, SIGUSR1);
		if (current_admin.debug_level >= DBG_ALL) {
			logit("getldap_get_rootDSE(): "
			    "SIGUSR1 signal sent to "
			    "parent process(%ld).\n", ppid);
		}
		signal_done = TRUE;
	}
	(void) mutex_unlock(&sig_mutex);

	thr_exit((void *) exitrc);

	return ((void *) NULL);
}

static int
getldap_init_serverInfo(server_info_t **head)
{
	char		**servers = NULL;
	int		rc = 0, i, exitrc = NS_LDAP_SUCCESS;
	ns_ldap_error_t *errorp = NULL;
	server_info_t	*info, *tail = NULL;

	*head = NULL;
	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_init_serverInfo()...\n");
	}
	rc = __s_api_getServers(&servers, &errorp);

	if (rc != NS_LDAP_SUCCESS) {
		logit("getldap_init_serverInfo: "
		    "__s_api_getServers failed.\n");
		if (errorp)
			__ns_ldap_freeError(&errorp);
		return (-1);
	}
	for (i = 0; servers[i] != NULL; i++) {
		info = (server_info_t *)calloc(1, sizeof (server_info_t));
		if (info == NULL) {
			logit("getldap_init_serverInfo: "
			    "not enough memory.\n");
			exitrc = NS_LDAP_MEMORY;
			break;
		}
		if (i == 0) {
			*head = info;
			tail  = info;
		} else {
			tail->next = info;
			tail  = info;
		}

		info->sinfo[0].addr		= strdup(servers[i]);
		if (info->sinfo[0].addr == NULL) {
			logit("getldap_init_serverInfo: "
			    "not enough memory.\n");
			exitrc = NS_LDAP_MEMORY;
			break;
		}
		info->sinfo[1].addr		= strdup(servers[i]);
		if (info->sinfo[1].addr == NULL) {
			logit("getldap_init_serverInfo: "
			    "not enough memory.\n");
			exitrc = NS_LDAP_MEMORY;
			break;
		}

		info->sinfo[0].type 		= INFO_RW_UNKNOWN;
		info->sinfo[1].type 		= INFO_RW_UNKNOWN;
		info->sinfo[0].info_status	= INFO_STATUS_UNKNOWN;
		info->sinfo[1].info_status	= INFO_STATUS_UNKNOWN;
		info->sinfo[0].server_status	= INFO_SERVER_UNKNOWN;
		info->sinfo[1].server_status	= INFO_SERVER_UNKNOWN;

		/*
		 * Assume at startup or after the configuration
		 * profile is refreshed, all servers are good.
		 */
		info->sinfo[0].prev_server_status =
		    INFO_SERVER_UP;
		info->sinfo[1].prev_server_status =
		    INFO_SERVER_UP;
		info->sinfo[0].hostname		= NULL;
		info->sinfo[1].hostname		= NULL;
		info->sinfo[0].rootDSE_data	= NULL;
		info->sinfo[1].rootDSE_data	= NULL;
		info->sinfo[0].errormsg 	= NULL;
		info->sinfo[1].errormsg 	= NULL;
		info->next 		= NULL;
	}
	__s_api_free2dArray(servers);
	if (exitrc != NS_LDAP_SUCCESS) {
		if (head && *head) {
			(void) getldap_destroy_serverInfo(*head);
			*head = NULL;
		}
	}
	return (exitrc);
}

static int
getldap_destroy_serverInfo(server_info_t *head)
{
	server_info_t	*info, *next;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_destroy_serverInfo()...\n");
	}

	if (head == NULL) {
		logit("getldap_destroy_serverInfo: "
		    "invalid serverInfo list.\n");
		return (-1);
	}

	for (info = head; info; info = next) {
		if (info->sinfo[0].addr)
			free(info->sinfo[0].addr);
		if (info->sinfo[1].addr)
			free(info->sinfo[1].addr);
		if (info->sinfo[0].hostname)
			free(info->sinfo[0].hostname);
		if (info->sinfo[1].hostname)
			free(info->sinfo[1].hostname);
		if (info->sinfo[0].rootDSE_data)
			free(info->sinfo[0].rootDSE_data);
		if (info->sinfo[1].rootDSE_data)
			free(info->sinfo[1].rootDSE_data);
		if (info->sinfo[0].errormsg)
			free(info->sinfo[0].errormsg);
		if (info->sinfo[1].errormsg)
			free(info->sinfo[1].errormsg);
		next = info->next;
		free(info);
	}
	return (NS_LDAP_SUCCESS);
}

static int
getldap_set_serverInfo(server_info_t *head, int reset_bindtime, info_op_t op)
{
	server_info_t	*info;
	int 		atleast1 = 0;
	thread_t	*tid;
	int 		num_threads = 0, i, j;
	void		*status;
	void		**paramVal = NULL;
	ns_ldap_error_t	*error = NULL;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_set_serverInfo()...\n");
	}

	if (head == NULL) {
		logit("getldap_set_serverInfo: "
		    "invalid serverInfo list.\n");
		return (-1);
	}

	/* Get the bind timeout value */
	if (reset_bindtime == 1) {
		tcptimeout = NS_DEFAULT_BIND_TIMEOUT * 1000;
		(void) __ns_ldap_getParam(NS_LDAP_BIND_TIME_P,
		    &paramVal, &error);
		if (paramVal != NULL && *paramVal != NULL) {
			/* convert to milliseconds */
			tcptimeout = **((int **)paramVal);
			tcptimeout *= 1000;
			(void) __ns_ldap_freeParam(&paramVal);
		}
		if (error)
			(void) __ns_ldap_freeError(&error);
	}

	for (info = head; info; info = info->next)
		num_threads++;

	if (num_threads == 0) {
		logit("getldap_set_serverInfo: "
		    "empty serverInfo list.\n");
		return (-1);
	}

	tid = (thread_t *) calloc(1, sizeof (thread_t) * num_threads);
	if (tid == NULL) {
		logit("getldap_set_serverInfo: "
		    "No memory to create thread ID list.\n");
		return (-1);
	}

	for (info = head, i = 0; info; info = info->next, i++) {
		if (thr_create(NULL, 0,
		    (void *(*)(void*))getldap_get_rootDSE,
		    (void *)info, 0, &tid[i])) {
			logit("getldap_set_serverInfo: "
			    "can not create thread %d.\n", i + 1);
			for (j = 0; j < i; j++)
				(void) thr_join(tid[j], NULL, NULL);
			free(tid);
			return (-1);
		}
	}

	for (i = 0; i < num_threads; i++) {
		if (thr_join(tid[i], NULL, &status) == 0) {
			if ((int)status == NS_LDAP_SUCCESS)
				atleast1 = 1;
		}
	}

	free(tid);

	if (op == INFO_OP_REFRESH)
		test_server_change(head);
	if (atleast1) {
		return (NS_LDAP_SUCCESS);
	} else
		return (-1);
}

/*
 * getldap_get_serverInfo processes the GETLDAPSERVER door request passed
 * to this function from getldap_serverInfo_op().
 * input:
 *   a buffer containing an empty string (e.g., input[0]='\0';) or a string
 *   as the "input" in printf(input, "%s%s%s%s", req, addrtype, DOORLINESEP,
 *   addr);
 *   where addr is the address of a server and
 *   req is one of the following:
 *   NS_CACHE_NEW:    send a new server address, addr is ignored.
 *   NS_CACHE_NORESP: send the next one, remove addr from list.
 *   NS_CACHE_NEXT:   send the next one, keep addr on list.
 *   NS_CACHE_WRITE:  send a non-replica server, if possible, if not, same
 *                    as NS_CACHE_NEXT.
 *   addrtype:
 *   NS_CACHE_ADDR_IP: return server address as is, this is default.
 *   NS_CACHE_ADDR_HOSTNAME: return both server address and its FQDN format,
 *			only self credential case requires such format.
 * output:
 *   a buffer containing server info in the following format:
 *   serveraddress DOORLINESEP [ serveraddress FQDN DOORLINESEP ]
 *   [ attr=value [DOORLINESEP attr=value ]...]
 *   For example: ( here | used as DOORLINESEP for visual purposes)
 *   1) simple bind and sasl/DIGEST-MD5 bind :
 *   1.2.3.4|supportedControl=1.1.1.1|supportedSASLmechanisms=EXTERNAL|
 *   supportedSASLmechanisms=GSSAPI
 *   2) sasl/GSSAPI bind (self credential):
 *   1.2.3.4|foo.sun.com|supportedControl=1.1.1.1|
 *   supportedSASLmechanisms=EXTERNAL|supportedSASLmechanisms=GSSAPI
 *   NOTE: caller should free this buffer when done using it
 */
static int
getldap_get_serverInfo(server_info_t *head, char *input,
		char **output, int *svr_removed)
{
	server_info_t	*info 	= NULL;
	server_info_t	*server	= NULL;
	char 		*addr	= NULL;
	char 		*req	= NULL;
	char 		req_new[] = NS_CACHE_NEW;
	char 		addr_type[] = NS_CACHE_ADDR_IP;
	int		matched = FALSE, len = 0, rc = 0;
	char		*ret_addr = NULL, *ret_addrFQDN = NULL;
	char		*new_addr = NULL;
	pid_t		pid;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_get_serverInfo()...\n");
	}

	if (input == NULL || output == NULL) {
		logit("getldap_get_serverInfo: "
		    "No input or output buffer.\n");
		return (-1);
	}

	*output = NULL;
	*svr_removed = FALSE;

	if (head == NULL) {
		logit("getldap_get_serverInfo: "
		    "invalid serverInfo list.\n");
		return (-1);
	}
	/*
	 * parse the input string to get req and addr,
	 * if input is empty, i.e., input[0] == '\0',
	 * treat it as an NS_CACHE_NEW request
	 */
	req = req_new;
	if (input[0] != '\0') {
		req = input;
		/* Save addr type flag */
		addr_type[0] = input[1];
		input[strlen(NS_CACHE_NEW)] = '\0';
		/* skip acion type flag, addr type flag and DOORLINESEP */
		addr = input + strlen(DOORLINESEP) + strlen(NS_CACHE_NEW)
		    + strlen(NS_CACHE_ADDR_IP);
	}
	/*
	 * if NS_CACHE_NEW,
	 * or the server info is new,
	 * starts from the
	 * beginning of the list
	 */
	if ((strcmp(req, NS_CACHE_NEW) == 0) ||
	    (head->sinfo[0].info_status == INFO_STATUS_NEW))
		matched = TRUE;
	for (info = head; info; info = info->next) {
		/*
		 * make sure the server info stays the same
		 * while the data is being processed
		 */

		/*
		 * This function is called to get server info list
		 * and pass it back to door call clients.
		 * Access the current copy (sinfo[0]) to get such
		 * information
		 */
		(void) mutex_lock(&info->mutex[0]);

		if (matched == FALSE &&
		    strcmp(info->sinfo[0].addr, addr) == 0) {
			matched = TRUE;
			if (strcmp(req, NS_CACHE_NORESP) == 0) {
				if (chg_is_called_from_nscd_or_peruser_nscd(
				    "REMOVE SERVER", &pid) == 0) {
					(void) mutex_unlock(&info->mutex[0]);
					if (current_admin.debug_level >=
					    DBG_ALL)
						logit("Only nscd can remove "
						    "servers. pid %ld", pid);
					continue;
				}

				/*
				 * if the information is new,
				 * give this server one more chance
				 */
				if (info->sinfo[0].info_status ==
				    INFO_STATUS_NEW &&
				    info->sinfo[0].server_status  ==
				    INFO_SERVER_UP) {
					server = info;
					break;
				} else {
					/*
					 * it is recommended that
					 * before removing the
					 * server from the list,
					 * the server should be
					 * contacted one more time
					 * to make sure that it is
					 * really unavailable.
					 * For now, just trust the client
					 * (i.e., the sldap library)
					 * that it knows what it is
					 * doing and would not try
					 * to mess up the server
					 * list.
					 */
					/*
					 * Make a copy of addr to contact
					 * it later. It's not doing it here
					 * to avoid long wait and possible
					 * recursion to contact an LDAP server.
					 */
					new_addr = strdup(info->sinfo[0].addr);
					if (new_addr)
						remove_server(new_addr);
					*svr_removed = TRUE;
					(void) mutex_unlock(&info->mutex[0]);
					break;
				}
			} else {
				/*
				 * req == NS_CACHE_NEXT or NS_CACHE_WRITE
				 */
				(void) mutex_unlock(&info->mutex[0]);
				continue;
			}
		}

		if (matched) {
			if (strcmp(req, NS_CACHE_WRITE) == 0) {
				if (info->sinfo[0].type ==
				    INFO_RW_WRITEABLE &&
				    info->sinfo[0].server_status  ==
				    INFO_SERVER_UP) {
					server = info;
					break;
				}
			} else if (info->sinfo[0].server_status ==
			    INFO_SERVER_UP) {
				server = info;
				break;
			}
		}

		(void) mutex_unlock(&info->mutex[0]);
	}

	if (server) {
		if (strcmp(addr_type, NS_CACHE_ADDR_HOSTNAME) == 0) {
			/*
			 * In SASL/GSSAPI case, a hostname is required for
			 * Kerberos's service principal.
			 * e.g.
			 * ldap/foo.sun.com@SUN.COM
			 */
			if (server->sinfo[0].hostname == NULL) {
				rc = __s_api_ip2hostname(server->sinfo[0].addr,
				    &server->sinfo[0].hostname);
				if (rc != NS_LDAP_SUCCESS) {
					(void) mutex_unlock(&info->mutex[0]);
					return (rc);
				}
				if (current_admin.debug_level >= DBG_ALL) {
					logit("getldap_get_serverInfo: "
					    "%s is converted to %s\n",
					    server->sinfo[0].addr,
					    server->sinfo[0].hostname);
				}
			}
			ret_addr = server->sinfo[0].addr;
			ret_addrFQDN = server->sinfo[0].hostname;

		} else
			ret_addr = server->sinfo[0].addr;


		len = strlen(ret_addr) +
		    strlen(server->sinfo[0].rootDSE_data) +
		    strlen(DOORLINESEP) + 1;
		if (ret_addrFQDN != NULL)
			len += strlen(ret_addrFQDN) + strlen(DOORLINESEP);
		*output = (char *)malloc(len);
		if (*output == NULL) {
			(void) mutex_unlock(&info->mutex[0]);
			return (NS_LDAP_MEMORY);
		}
		if (ret_addrFQDN == NULL)
			(void) snprintf(*output, len, "%s%s%s",
			    ret_addr, DOORLINESEP,
			    server->sinfo[0].rootDSE_data);
		else
			(void) snprintf(*output, len, "%s%s%s%s%s",
			    ret_addr, DOORLINESEP,
			    ret_addrFQDN, DOORLINESEP,
			    server->sinfo[0].rootDSE_data);
		server->sinfo[0].info_status = INFO_STATUS_OLD;
		(void) mutex_unlock(&info->mutex[0]);
		return (NS_LDAP_SUCCESS);
	}
	else
		return (-99);
}

/*
 * Format previous and next refresh time
 */
static int
getldap_format_refresh_time(char **output, time_t *prev, time_t *next)
{
#define	TIME_FORMAT	"%Y/%m/%d %H:%M:%S"
#define	TIME_HEADER1	"  Previous refresh time: "
#define	TIME_HEADER2	"  Next refresh time:     "
	int		hdr1_len = strlen(gettext(TIME_HEADER1));
	int		hdr2_len = strlen(gettext(TIME_HEADER2));
	struct	tm 	tm;
	char		nbuf[256];
	char		pbuf[256];
	int		len;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_format_refresh_time()...\n");
	}

	*output = NULL;

	/* format the time of previous refresh  */
	if (*prev != 0) {
		(void) localtime_r(prev, &tm);
		(void) strftime(pbuf, sizeof (pbuf) - 1, TIME_FORMAT, &tm);
	} else {
		(void) strcpy(pbuf, gettext("NOT DONE"));
	}

	/* format the time of next refresh  */
	if (*next != 0) {
		(void) localtime_r(next, &tm);
		(void) strftime(nbuf, sizeof (nbuf) - 1, TIME_FORMAT, &tm);
	} else {
		(void) strcpy(nbuf, gettext("NOT SET"));
	}

	len = hdr1_len + hdr2_len + strlen(nbuf) +
	    strlen(pbuf) + 2 * strlen(DOORLINESEP) + 1;

	*output = malloc(len);
	if (*output == NULL)
		return (-1);

	(void) snprintf(*output, len, "%s%s%s%s%s%s",
	    gettext(TIME_HEADER1), pbuf, DOORLINESEP,
	    gettext(TIME_HEADER2), nbuf, DOORLINESEP);

	return (NS_LDAP_SUCCESS);
}

/*
 * getldap_get_server_stat processes the GETSTAT request passed
 * to this function from getldap_serverInfo_op().
 * output:
 *   a buffer containing info for all the servers.
 *   For each server, the data is in the following format:
 *   server: server address or name, status: unknown|up|down|removed DOORLINESEP
 *   for example: ( here | used as DOORLINESEP for visual purposes)
 *   server: 1.2.3.4, status: down|server: 2.2.2.2, status: up|
 *   NOTE: caller should free this buffer when done using it
 */
static int
getldap_get_server_stat(server_info_t *head, char **output,
		time_t *prev, time_t *next)
{
#define	S_HEADER	"Server information: "
#define	S_FORMAT	"  server: %s, status: %s%s"
#define	S_ERROR		"    error message: %s%s"
	server_info_t	*info 	= NULL;
	int	header_len = strlen(gettext(S_HEADER));
	int	format_len = strlen(gettext(S_FORMAT));
	int	error_len = strlen(gettext(S_ERROR));
	int	len = header_len + strlen(DOORLINESEP);
	int	len1 = 0;
	char	*status, *output1 = NULL, *tmpptr;

	*output = NULL;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_get_server_stat()...\n");
	}

	if (head == NULL) {
		logit("getldap_get_server_stat: "
		    "invalid serverInfo list.\n");
		return (-1);
	}

	/* format previous and next refresh time */
	(void) getldap_format_refresh_time(&output1, prev, next);
	if (output1 == NULL)
		return (-1);
	len += strlen(output1);
	len1 = len + strlen(DOORLINESEP) + 1;

	*output = (char *)calloc(1, len1);
	if (*output == NULL) {
		free(output1);
		return (-1);
	}

	/* insert header string and refresh time info */
	(void) snprintf(*output, len1, "%s%s%s",
	    gettext(S_HEADER), DOORLINESEP, output1);

	for (info = head; info; info = info->next) {

		/*
		 * make sure the server info stays the same
		 * while the data is being processed
		 */
		(void) mutex_lock(&info->mutex[1]);

		/*
		 * When the updating process is under way(getldap_get_rootDSE)
		 * the update copy(sinfo[1] is the latest copy.
		 * When the updating process
		 * is done, the current copy (sinfo[0]) has the latest status,
		 * which is still identical to the update copy.
		 * So update copy has the latest status.
		 * Use the update copy(sinfo[1]) to show status
		 * (ldap_cachemgr -g).
		 *
		 */

		switch (info->sinfo[1].server_status) {
		case INFO_SERVER_UNKNOWN:
			status = gettext("UNKNOWN");
			break;
		case INFO_SERVER_CONNECTING:
			status = gettext("CONNECTING");
			break;
		case INFO_SERVER_UP:
			status = gettext("UP");
			break;
		case INFO_SERVER_ERROR:
			status = gettext("ERROR");
			break;
		case INFO_SERVER_REMOVED:
			status = gettext("REMOVED");
			break;
		}

		len += format_len + strlen(status) +
		    strlen(info->sinfo[1].addr) +
		    strlen(DOORLINESEP);
		if (info->sinfo[1].errormsg != NULL)
			len += error_len +
			    strlen(info->sinfo[1].errormsg) +
			    strlen(DOORLINESEP);

		tmpptr = (char *)realloc(*output, len);
		if (tmpptr == NULL) {
			free(output1);
			free(*output);
			*output = NULL;
			(void) mutex_unlock(&info->mutex[1]);
			return (-1);
		} else
			*output = tmpptr;

		/* insert server IP addr or name and status */
		len1 = len - strlen(*output);
		(void) snprintf(*output + strlen(*output), len1,
		    gettext(S_FORMAT), info->sinfo[1].addr,
		    status, DOORLINESEP);
		/* insert error message if any */
		len1 = len - strlen(*output);
		if (info->sinfo[1].errormsg != NULL)
			(void) snprintf(*output + strlen(*output), len1,
			    gettext(S_ERROR),
			    info->sinfo[1].errormsg,
			    DOORLINESEP);

		(void) mutex_unlock(&info->mutex[1]);

	}

	free(output1);
	return (NS_LDAP_SUCCESS);
}

/*
 * Format and return the refresh time statistics
 */
static int
getldap_get_refresh_stat(char **output)
{
#define	R_HEADER0	"Configuration refresh information: "
#define	R_HEADER1	"  Configured to NO REFRESH."
	int		hdr0_len = strlen(gettext(R_HEADER0));
	int		hdr1_len = strlen(gettext(R_HEADER1));
	int		cache_ttl = -1, len = 0;
	time_t 		expire = 0;
	void		**paramVal = NULL;
	ns_ldap_error_t	*errorp = NULL;
	char		*output1 = NULL;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_get_refresh_stat()...\n");
	}

	*output = NULL;

	/* get configured cache TTL */
	if ((__ns_ldap_getParam(NS_LDAP_CACHETTL_P,
	    &paramVal, &errorp) == NS_LDAP_SUCCESS) &&
	    paramVal != NULL &&
	    (char *)*paramVal != NULL) {
			cache_ttl = atol((char *)*paramVal);
	} else {
		if (errorp)
			__ns_ldap_freeError(&errorp);
	}
	(void) __ns_ldap_freeParam(&paramVal);

	/* cound not get cache TTL */
	if (cache_ttl == -1)
		return (-1);

	if (cache_ttl == 0) {
		len = hdr0_len + hdr1_len +
		    2 * strlen(DOORLINESEP) + 1;
		*output = malloc(len);
		if (*output == NULL)
			return (-1);
		(void) snprintf(*output, len, "%s%s%s%s",
		    gettext(R_HEADER0), DOORLINESEP,
		    gettext(R_HEADER1), DOORLINESEP);
	} else {

		/* get configuration expiration time */
		if ((__ns_ldap_getParam(NS_LDAP_EXP_P,
		    &paramVal, &errorp) == NS_LDAP_SUCCESS) &&
		    paramVal != NULL &&
		    (char *)*paramVal != NULL) {
				expire = (time_t)atol((char *)*paramVal);
		} else {
			if (errorp)
				__ns_ldap_freeError(&errorp);
		}

		(void) __ns_ldap_freeParam(&paramVal);

		/* cound not get expiration time */
		if (expire == -1)
			return (-1);

		/* format previous and next refresh time */
		(void) getldap_format_refresh_time(&output1,
		    &prev_refresh_time, &expire);
		if (output1 == NULL)
			return (-1);

		len = hdr0_len + strlen(output1) +
		    2 * strlen(DOORLINESEP) + 1;
		*output = malloc(len);
		if (*output == NULL) {
			free(output1);
			return (-1);
		}
		(void) snprintf(*output, len, "%s%s%s%s",
		    gettext(R_HEADER0), DOORLINESEP,
		    output1, DOORLINESEP);
		free(output1);
	}

	return (NS_LDAP_SUCCESS);
}

static int
getldap_get_cacheTTL()
{
	void		**paramVal = NULL;
	ns_ldap_error_t	*error;
	int		rc = 0, cachettl;


	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_get_cacheTTL()....\n");
	}

	if ((rc = __ns_ldap_getParam(NS_LDAP_CACHETTL_P,
	    &paramVal, &error)) != NS_LDAP_SUCCESS) {
		if (error != NULL && error->message != NULL)
			logit("Error: Unable to get configuration "
			    "refresh TTL: %s\n",
			    error->message);
		else {
			char *tmp;

			__ns_ldap_err2str(rc, &tmp);
			logit("Error: Unable to get configuration "
			    "refresh TTL: %s\n", tmp);
		}
		(void) __ns_ldap_freeParam(&paramVal);
		(void) __ns_ldap_freeError(&error);
		return (-1);
	}
	if (paramVal == NULL || (char *)*paramVal == NULL)
			return (-1);
	cachettl = atol((char *)*paramVal);
	(void) __ns_ldap_freeParam(&paramVal);
	return (cachettl);
}


/*
 * This function implements the adaptive server list refresh
 * algorithm used by ldap_cachemgr. The idea is to have the
 * refresh TTL adjust itself between maximum and minimum
 * values. If the server list has been walked three times
 * in a row without errors, the TTL will be doubled. This will
 * be done repeatedly until the maximum value is reached
 * or passed. If passed, the maximum value will be used.
 * If any time a server is found to be down/bad, either
 * after another server list walk or informed by libsldap via
 * the GETLDAPSERVER door calls, the TTL will be set to half
 * of its value, again repeatedly, but no less than the minimum
 * value. Also, at any time, if all the servers on the list
 * are found to be down/bad, the TTL will be set to minimum,
 * so that a "no-server" refresh loop should be entered to try
 * to find a good server as soon as possible. The caller
 * could check the no_gd_server flag for this situation.
 * The maximum and minimum values are initialized when the input
 * refresh_ttl is set to zero, this should occur during
 * ldap_cachemgr startup or every time the server list is
 * recreated after the configuration profile is refreshed
 * from an LDAP server. The maximum is set to the value of
 * the NS_LDAP_CACHETTL parameter (configuration profile
 * refresh TTL), but if it is zero (never refreshed) or can
 * not be retrieved, the maximum is set to the macro
 * REFRESHTTL_MAX (12 hours) defined below. The minimum is
 * set to REFRESHTTL_MIN, which is the TCP connection timeout
 * (tcptimeout) set via the LDAP API ldap_set_option()
 * with the new LDAP_X_OPT_CONNECT_TIMEOUT option plus 10 seconds.
 * This accounts for the maximum possible timeout value for an
 * LDAP TCP connect call.The first refresh TTL, initial value of
 * refresh_ttl, will be set to the smaller of the two,
 * REFRESHTTL_REGULAR (10 minutes) or (REFRESHTTL_MAX + REFRESHTTL_MIN)/2.
 * The idea is to have a low starting value and have the value
 * stay low if the network/server is unstable, but eventually
 * the value will move up to maximum and stay there if the
 * network/server is stable.
 */
static int
getldap_set_refresh_ttl(server_info_t *head, int *refresh_ttl,
		int *no_gd_server)
{
#define	REFRESHTTL_REGULAR	600
#define	REFRESHTTL_MAX		43200
/* tcptimeout is in milliseconds */
#define	REFRESHTTL_MIN		(tcptimeout/1000) + 10
#define	UP_REFRESH_TTL_NUM	2

	static mutex_t		refresh_mutex;
	static int		refresh_ttl_max = 0;
	static int		refresh_ttl_min = 0;
	static int		num_walked_ok = 0;
	int			num_servers = 0;
	int			num_good_servers = 0;
	int			num_prev_good_servers = 0;
	server_info_t		*info;

	/* allow one thread at a time */
	(void) mutex_lock(&refresh_mutex);

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_set_refresh_ttl()...\n");
	}

	if (!head || !refresh_ttl || !no_gd_server) {
		logit("getldap_set_refresh_ttl: head is "
		    "NULL or refresh_ttl is NULL or "
		    "no_gd_server is NULL");
		(void) mutex_unlock(&refresh_mutex);
		return (-1);
	}
	*no_gd_server = FALSE;

	/*
	 * init max. min. TTLs if first time through or a fresh one
	 */
	if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) {
		logit("getldap_set_refresh_ttl:(1) refresh ttl is %d "
		    "seconds\n", *refresh_ttl);
	}
	if (*refresh_ttl == 0) {
		num_walked_ok = 0;
		/*
		 * init cache manager server list TTL:
		 *
		 * init the min. TTL to
		 * REFRESHTTL_MIN ( 2*(TCP MSL) + 10 seconds)
		 */
		refresh_ttl_min = REFRESHTTL_MIN;

		/*
		 * try to set the max. TTL to
		 * configuration refresh TTL (NS_LDAP_CACHETTL),
		 * if error (-1), or never refreshed (0),
		 * set it to REFRESHTTL_MAX (12 hours)
		 */
		refresh_ttl_max = getldap_get_cacheTTL();
		if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) {
			logit("getldap_set_refresh_ttl:(2) refresh ttl is %d "
			    "seconds\n", *refresh_ttl);
			logit("getldap_set_refresh_ttl:(2) max ttl is %d, "
			    "min ttl is %d seconds\n",
			    refresh_ttl_max, refresh_ttl_min);
		}
		if (refresh_ttl_max <= 0)
			refresh_ttl_max = REFRESHTTL_MAX;
		else if (refresh_ttl_max < refresh_ttl_min)
			refresh_ttl_max = refresh_ttl_min;

		/*
		 * init the first TTL to the smaller of the two:
		 * REFRESHTTL_REGULAR ( 10 minutes),
		 * (refresh_ttl_max + refresh_ttl_min)/2
		 */
		*refresh_ttl = REFRESHTTL_REGULAR;
		if (*refresh_ttl > (refresh_ttl_max + refresh_ttl_min) / 2)
			*refresh_ttl = (refresh_ttl_max + refresh_ttl_min) / 2;
		if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) {
			logit("getldap_set_refresh_ttl:(3) refresh ttl is %d "
			    "seconds\n", *refresh_ttl);
			logit("getldap_set_refresh_ttl:(3) max ttl is %d, "
			    "min ttl is %d seconds\n",
			    refresh_ttl_max, refresh_ttl_min);
		}
	}

	/*
	 * get the servers statistics:
	 * number of servers on list
	 * number of good servers on list
	 * number of pevious good servers on list
	 */
	for (info = head; info; info = info->next) {
		num_servers++;
		(void) mutex_lock(&info->mutex[0]);
		if (info->sinfo[0].server_status  == INFO_SERVER_UP)
			num_good_servers++;
		/*
		 * Server's previous status could be UNKNOWN
		 * only between the very first and second
		 * refresh. Treat that UNKNOWN status as up
		 */
		if (info->sinfo[0].prev_server_status
		    == INFO_SERVER_UP ||
		    info->sinfo[0].prev_server_status
		    == INFO_SERVER_UNKNOWN)
			num_prev_good_servers++;
		(void) mutex_unlock(&info->mutex[0]);
	}

	/*
	 * if the server list is walked three times in a row
	 * without problems, double the refresh TTL but no more
	 * than the max. refresh TTL
	 */
	if (num_good_servers == num_servers) {
		num_walked_ok++;
		if (num_walked_ok > UP_REFRESH_TTL_NUM)  {

			*refresh_ttl = *refresh_ttl * 2;
			if (*refresh_ttl > refresh_ttl_max)
				*refresh_ttl = refresh_ttl_max;

			num_walked_ok = 0;
		}
		if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) {
			logit("getldap_set_refresh_ttl:(4) refresh ttl is %d "
			    "seconds\n", *refresh_ttl);
		}
	} else if (num_good_servers == 0) {
		/*
		 * if no good server found,
		 * set refresh TTL to miminum
		 */
		*refresh_ttl = refresh_ttl_min;
		*no_gd_server = TRUE;
		num_walked_ok = 0;
		if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) {
			logit("getldap_set_refresh_ttl:(5) refresh ttl is %d "
			    "seconds\n", *refresh_ttl);
		}
	} else if (num_prev_good_servers > num_good_servers) {
		/*
		 * if more down/bad servers found,
		 * decrease the refresh TTL by half
		 * but no less than the min. refresh TTL
		 */
		*refresh_ttl = *refresh_ttl / 2;
		if (*refresh_ttl < refresh_ttl_min)
			*refresh_ttl = refresh_ttl_min;
		num_walked_ok = 0;
		logit("getldap_set_refresh_ttl:(6) refresh ttl is %d "
		    "seconds\n", *refresh_ttl);

	}

	if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) {
		logit("getldap_set_refresh_ttl:(7) refresh ttl is %d seconds\n",
		    *refresh_ttl);
	}
	(void) mutex_unlock(&refresh_mutex);
	return (0);
}

static int
getldap_serverInfo_op(info_op_t op, char *input, char **output)
{

	static rwlock_t 	info_lock = DEFAULTRWLOCK;
	static rwlock_t 	info_lock_old = DEFAULTRWLOCK;
	static mutex_t		info_mutex;
	static cond_t		info_cond;
	static int		creating = FALSE;
	static int		refresh_ttl = 0;
	static int		sec_to_refresh = 0;
	static int		in_no_server_mode = FALSE;

	static server_info_t 	*serverInfo = NULL;
	static server_info_t 	*serverInfo_old = NULL;
	server_info_t 		*serverInfo_1;
	int 			is_creating;
	int 			err, no_server_good = FALSE;
	int			server_removed = FALSE;
	int			fall_thru = FALSE;
	static struct timespec	timeout;
	struct timespec		new_timeout;
	struct timeval		tp;
	static time_t		prev_refresh = 0, next_refresh = 0;
	ns_server_status_t		changed = 0;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_serverInfo_op()...\n");
	}
	switch (op) {
	case INFO_OP_CREATE:
		if (current_admin.debug_level >= DBG_ALL) {
			logit("operation is INFO_OP_CREATE...\n");
		}

		/*
		 * indicate that the server info is being
		 * (re)created, so that the refresh thread
		 * will not refresh the info list right
		 * after the list got (re)created
		 */
		(void) mutex_lock(&info_mutex);
		is_creating = creating;
		creating = TRUE;
		(void) mutex_unlock(&info_mutex);

		if (is_creating)
			break;
		/*
		 * create an empty info list
		 */
		(void) getldap_init_serverInfo(&serverInfo_1);
		/*
		 * exit if list not created
		 */
		if (serverInfo_1 == NULL) {
			(void) mutex_lock(&info_mutex);
			creating = FALSE;
			(void) mutex_unlock(&info_mutex);
			break;
		}
		/*
		 * make the new server info available:
		 * use writer lock here, so that the switch
		 * is done after all the reader locks have
		 * been released.
		 */
		(void) rw_wrlock(&info_lock);
		serverInfo = serverInfo_1;
		/*
		 * if this is the first time
		 * the server list is being created,
		 * (i.e., serverInfo_old is NULL)
		 * make the old list same as the new
		 * so the GETSERVER code can do its work
		 */
		if (serverInfo_old == NULL)
			serverInfo_old = serverInfo_1;
		(void) rw_unlock(&info_lock);

		/*
		 * fill the new info list
		 */
		(void) rw_rdlock(&info_lock);
		/* reset bind time (tcptimeout) */
		(void) getldap_set_serverInfo(serverInfo, 1, INFO_OP_CREATE);

		(void) mutex_lock(&info_mutex);
		/*
		 * set cache manager server list TTL,
		 * set refresh_ttl to zero to indicate a fresh one
		 */
		refresh_ttl = 0;
		(void) getldap_set_refresh_ttl(serverInfo,
		    &refresh_ttl, &no_server_good);
		sec_to_refresh = refresh_ttl;

		/* statistics: previous refresh time */
		if (gettimeofday(&tp, NULL) == 0)
			prev_refresh = tp.tv_sec;

		creating = FALSE;

		/*
		 * if no server found or available,
		 * tell the server info refresh thread
		 * to start the "no-server" refresh loop
		 * otherwise reset the in_no_server_mode flag
		 */
		if (no_server_good) {
			sec_to_refresh = 0;
			in_no_server_mode = TRUE;
		} else
			in_no_server_mode = FALSE;
		/*
		 * awake the sleeping refresh thread
		 */
		(void) cond_signal(&info_cond);

		(void) mutex_unlock(&info_mutex);
		(void) rw_unlock(&info_lock);

		/*
		 * delete the old server info
		 */
		(void) rw_wrlock(&info_lock_old);
		if (serverInfo_old != serverInfo)
			(void) getldap_destroy_serverInfo(serverInfo_old);
		/*
		 * serverInfo_old needs to be the same as
		 * serverinfo now.
		 * it will be used by GETSERVER processing.
		 */
		serverInfo_old = serverInfo;
		(void) rw_unlock(&info_lock_old);
		break;
	case INFO_OP_DELETE:
		if (current_admin.debug_level >= DBG_ALL) {
			logit("operation is INFO_OP_DELETE...\n");
		}
		/*
		 * use writer lock here, so that the delete would
		 * not start until all the reader locks have
		 * been released.
		 */
		(void) rw_wrlock(&info_lock);
		if (serverInfo)
			(void) getldap_destroy_serverInfo(serverInfo);
		serverInfo = NULL;
		(void) rw_unlock(&info_lock);
		break;
	case INFO_OP_REFRESH:
		if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) {
			logit("operation is INFO_OP_REFRESH...\n");
		}
		/*
		 * if server info is currently being
		 * (re)created, do nothing
		 */
		(void) mutex_lock(&info_mutex);
		is_creating = creating;
		(void) mutex_unlock(&info_mutex);
		if (is_creating)
			break;

		(void) rw_rdlock(&info_lock);
		if (serverInfo) {
			/* do not reset bind time (tcptimeout) */
			(void) getldap_set_serverInfo(serverInfo, 0,
			    INFO_OP_REFRESH);

			(void) mutex_lock(&info_mutex);

			/* statistics: previous refresh time */
			if (gettimeofday(&tp, NULL) == 0)
				prev_refresh = tp.tv_sec;
			/*
			 * set cache manager server list TTL
			 */
			(void) getldap_set_refresh_ttl(serverInfo,
			    &refresh_ttl, &no_server_good);
			/*
			 * if no good server found,
			 * tell the server info refresh thread
			 * to start the "no-server" refresh loop
			 * otherwise reset the in_no_server_mode flag
			 */
			if (no_server_good) {
				in_no_server_mode = TRUE;
				sec_to_refresh = 0;
			} else {
				in_no_server_mode = FALSE;
				sec_to_refresh = refresh_ttl;
			}
			if (current_admin.debug_level >=
			    DBG_SERVER_LIST_REFRESH) {
				logit("getldap_serverInfo_op("
				    "INFO_OP_REFRESH):"
				    " seconds refresh: %d second(s)....\n",
				    sec_to_refresh);
			}
			(void) mutex_unlock(&info_mutex);
		}
		(void) rw_unlock(&info_lock);

		break;
	case INFO_OP_REFRESH_WAIT:
		if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) {
			logit("operation is INFO_OP_REFRESH_WAIT...\n");
		}
		(void) cond_init(&info_cond, NULL, NULL);
		(void) mutex_lock(&info_mutex);
		err = 0;
		while (err != ETIME) {
			int sleeptime;
			/*
			 * if need to go into the "no-server" refresh
			 * loop, set timout value to
			 * REFRESH_DELAY_WHEN_NO_SERVER
			 */
			if (sec_to_refresh == 0) {
				sec_to_refresh = refresh_ttl;
				timeout.tv_sec = time(NULL) +
				    REFRESH_DELAY_WHEN_NO_SERVER;
				sleeptime = REFRESH_DELAY_WHEN_NO_SERVER;
				if (current_admin.debug_level >=
				    DBG_SERVER_LIST_REFRESH) {
					logit("getldap_serverInfo_op("
					    "INFO_OP_REFRESH_WAIT):"
					    " entering no-server "
					    "refresh loop...\n");
				}
			} else {
				timeout.tv_sec = time(NULL) + sec_to_refresh;
				sleeptime = sec_to_refresh;
			}
			timeout.tv_nsec = 0;

			/* statistics: next refresh time */
			next_refresh = timeout.tv_sec;

			if (current_admin.debug_level >=
			    DBG_SERVER_LIST_REFRESH) {
				logit("getldap_serverInfo_op("
				    "INFO_OP_REFRESH_WAIT):"
				    " about to sleep for %d second(s)...\n",
				    sleeptime);
			}
			err = cond_timedwait(&info_cond,
			    &info_mutex, &timeout);
		}
		(void) cond_destroy(&info_cond);
		(void) mutex_unlock(&info_mutex);
		break;
	case INFO_OP_GETSERVER:
		if (current_admin.debug_level >= DBG_ALL) {
			logit("operation is INFO_OP_GETSERVER...\n");
		}
		*output = NULL;
		/*
		 * GETSERVER processing always use
		 * serverInfo_old to retrieve server infomation.
		 * serverInfo_old is equal to serverInfo
		 * most of the time, except when a new
		 * server list is being created.
		 * This is why the check for is_creating
		 * is needed below.
		 */
		(void) rw_rdlock(&info_lock_old);

		if (serverInfo_old == NULL) {
			(void) rw_unlock(&info_lock_old);
			break;
		} else
			(void) getldap_get_serverInfo(serverInfo_old,
			    input, output, &server_removed);
		(void) rw_unlock(&info_lock_old);

		/*
		 * Return here and let remove server thread do its job in
		 * another thread. It executes INFO_OP_REMOVESERVER code later.
		 */
		if (server_removed)
			break;

		fall_thru = TRUE;

		/* FALL THROUGH */

	case INFO_OP_REMOVESERVER:
		/*
		 * INFO_OP_GETSERVER and INFO_OP_REMOVESERVER share the
		 * following code except (!fall thru) part.
		 */

		/*
		 * if server info is currently being
		 * (re)created, do nothing
		 */

		(void) mutex_lock(&info_mutex);
		is_creating = creating;
		(void) mutex_unlock(&info_mutex);
		if (is_creating)
			break;

		if (!fall_thru) {
			if (current_admin.debug_level >= DBG_ALL)
				logit("operation is INFO_OP_REMOVESERVER...\n");
			(void) rw_rdlock(&info_lock_old);
			changed = set_server_status(input, serverInfo_old);
			(void) rw_unlock(&info_lock_old);
			if (changed)
				create_buf_and_notify(input, changed);
			else
				break;
		}

		/*
		 * set cache manager server list TTL if necessary
		 */
		if (*output == NULL || changed) {
			(void) rw_rdlock(&info_lock);
			(void) mutex_lock(&info_mutex);

			(void) getldap_set_refresh_ttl(serverInfo,
			    &refresh_ttl, &no_server_good);

			/*
			 * if no good server found, need to go into
			 * the "no-server" refresh loop
			 * to find a server as soon as possible
			 * otherwise reset the in_no_server_mode flag
			 */
			if (no_server_good) {
				/*
				 * if already in no-server mode,
				 * don't brother
				 */
				if (in_no_server_mode == FALSE) {
					sec_to_refresh = 0;
					in_no_server_mode = TRUE;
					(void) cond_signal(&info_cond);
				}
				(void) mutex_unlock(&info_mutex);
				(void) rw_unlock(&info_lock);
				break;
			} else {
				in_no_server_mode = FALSE;
				sec_to_refresh = refresh_ttl;
			}
			/*
			 * if the refresh thread will be timed out
			 * longer than refresh_ttl seconds,
			 * wake it up to make it wait on the new
			 * time out value
			 */
			new_timeout.tv_sec = time(NULL) + refresh_ttl;
			if (new_timeout.tv_sec < timeout.tv_sec)
				(void) cond_signal(&info_cond);

			(void) mutex_unlock(&info_mutex);
			(void) rw_unlock(&info_lock);
		}
		break;
	case INFO_OP_GETSTAT:
		if (current_admin.debug_level >= DBG_ALL) {
			logit("operation is INFO_OP_GETSTAT...\n");
		}
		*output = NULL;
		(void) rw_rdlock(&info_lock);
		if (serverInfo) {
			(void) getldap_get_server_stat(serverInfo,
			    output, &prev_refresh, &next_refresh);
		}
		(void) rw_unlock(&info_lock);
		break;
	default:
		logit("getldap_serverInfo_op(): "
		    "invalid operation code (%d).\n", op);
		return (-1);
		break;
	}
	return (NS_LDAP_SUCCESS);
}

void
getldap_serverInfo_refresh()
{
	int always = 1;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_serverInfo_refresh()...\n");
	}

	/* create the server info list */
	(void) getldap_serverInfo_op(INFO_OP_CREATE, NULL, NULL);

	while (always) {
		/*
		 * the operation INFO_OP_REFRESH_WAIT
		 * causes this thread to wait until
		 * it is time to do refresh,
		 * see getldap_serverInfo_op() for details
		 */
		(void) getldap_serverInfo_op(INFO_OP_REFRESH_WAIT, NULL, NULL);
		(void) getldap_serverInfo_op(INFO_OP_REFRESH, NULL, NULL);
	}
}

void
getldap_getserver(LineBuf *config_info, ldap_call_t *in)
{
	char 		req[] = "0";

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_getserver()...\n");
	}

	config_info->len = 0;

	/* make sure the request is valid */
	req[0] = (in->ldap_u.servername)[0];
	if ((req[0] != '\0') &&
	    (strcmp(req, NS_CACHE_NEW) != 0) &&
	    (strcmp(req, NS_CACHE_NORESP)  != 0) &&
	    (strcmp(req, NS_CACHE_NEXT)    != 0) &&
	    (strcmp(req, NS_CACHE_WRITE)   != 0)) {
		return;
	}

	(void) getldap_serverInfo_op(INFO_OP_GETSERVER,
	    in->ldap_u.domainname, &config_info->str);

	if (config_info->str == NULL)
		return;

	config_info->len = strlen(config_info->str) + 1;

	if (current_admin.debug_level >= DBG_PROFILE_REFRESH) {
		/* Log server IP */
		char	*ptr,
		    separator;
		ptr = strstr(config_info->str, DOORLINESEP);
		if (ptr) {
			separator = *ptr;
			*ptr = '\0';
			logit("getldap_getserver: got server %s\n",
			    config_info->str);
			*ptr = separator;
		} else
			logit("getldap_getserver: Missing %s."
			    " Internal error\n", DOORLINESEP);
	}
}

void
getldap_get_cacheData(LineBuf *config_info, ldap_call_t *in)
{
	char	*instr = NULL;
	int	datatype = CACHE_MAP_UNKNOWN;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_get_cacheData()...\n");
	}

	config_info->len = 0;
	config_info->str = NULL;

	/* make sure the request is valid */
	if (strncmp(in->ldap_u.servername,
	    NS_CACHE_DN2DOMAIN, strlen(NS_CACHE_DN2DOMAIN)) == 0)
		datatype = CACHE_MAP_DN2DOMAIN;

	if (datatype == CACHE_MAP_UNKNOWN)
		return;

	instr = strstr(in->ldap_u.servername, DOORLINESEP);
	if (instr == NULL)
		return;
	instr += strlen(DOORLINESEP);
	if (*instr == '\0')
		return;

	(void) getldap_cache_op(CACHE_OP_FIND, datatype,
	    instr, &config_info->str);

	if (config_info->str != NULL) {
		config_info->len = strlen(config_info->str) + 1;
	}
}

int
getldap_set_cacheData(ldap_call_t *in)
{
	char	*instr1 = NULL;
	char	*instr2 = NULL;
	int	datatype = CACHE_MAP_UNKNOWN;
	int	rc = 0;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_set_cacheData()...\n");
	}

	/* make sure the request is valid */
	if (strncmp(in->ldap_u.servername,
	    NS_CACHE_DN2DOMAIN, strlen(NS_CACHE_DN2DOMAIN)) == 0)
		datatype = CACHE_MAP_DN2DOMAIN;

	if (datatype == CACHE_MAP_UNKNOWN)
		return (-1);

	instr1 = strstr(in->ldap_u.servername, DOORLINESEP);
	if (instr1 == NULL)
		return (-1);
	*instr1 = '\0';
	instr1 += strlen(DOORLINESEP);
	if (*instr1 == '\0')
		return (-1);
	instr2 = strstr(instr1, DOORLINESEP);
	if (instr2 == NULL)
		return (-1);
	*instr2 = '\0';
	instr2 += strlen(DOORLINESEP);
	if (*instr2 == '\0')
		return (-1);

	rc = getldap_cache_op(CACHE_OP_ADD, datatype,
	    instr1, &instr2);
	if (rc != NS_LDAP_SUCCESS)
		return (-1);

	return (0);
}

void
getldap_get_cacheStat(LineBuf *stat_info)
{
	char	*foutstr = NULL;
	char	*soutstr = NULL;
	char	*coutstr = NULL;
	int	infoSize;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_get_cacheStat()...\n");
	}

	stat_info->str = NULL;
	stat_info->len = 0;

	/* get refersh statisitcs */
	(void) getldap_get_refresh_stat(&foutstr);
	if (foutstr == NULL)
		return;

	/* get server statisitcs */
	(void) getldap_serverInfo_op(INFO_OP_GETSTAT, NULL, &soutstr);
	if (soutstr == NULL) {
		free(foutstr);
		return;
	}
	/* get cache data statisitcs */
	(void) getldap_cache_op(CACHE_OP_GETSTAT, NULL, NULL, &coutstr);
	if (coutstr == NULL) {
		free(foutstr);
		free(soutstr);
		return;
	}

	infoSize = strlen(foutstr) + strlen(soutstr) + strlen(coutstr) + 3;
	stat_info->str = calloc(infoSize, sizeof (char));
	if (stat_info->str != NULL) {
		(void) strncpy(stat_info->str,
		    foutstr,
		    strlen(foutstr) + 1);
		(void) strncat(stat_info->str,
		    soutstr,
		    strlen(soutstr) + 1);
		(void) strncat(stat_info->str,
		    coutstr,
		    strlen(coutstr) + 1);
		stat_info->len = infoSize;
	}

	free(foutstr);
	free(soutstr);
	free(coutstr);
}

static int
checkupdate(int sighup)
{
	int	value;

	(void) rw_wrlock(&ldap_lock);
	value = sighup;
	(void) rw_unlock(&ldap_lock);

	return (value == TRUE);
}


static int
update_from_profile(int *change_status)
{
	ns_ldap_result_t *result = NULL;
	char		searchfilter[BUFSIZ];
	ns_ldap_error_t	*error;
	int		rc;
	void		**paramVal = NULL;
	ns_config_t	*ptr = NULL;
	char		*profile = NULL;
	char		errstr[MAXERROR];

	if (current_admin.debug_level >= DBG_ALL) {
		logit("update_from_profile....\n");
	}
	do {
		(void) rw_wrlock(&ldap_lock);
		sighup_update = FALSE;
		(void) rw_unlock(&ldap_lock);

		if ((rc = __ns_ldap_getParam(NS_LDAP_PROFILE_P,
		    &paramVal, &error)) != NS_LDAP_SUCCESS) {
			if (error != NULL && error->message != NULL)
				logit("Error: Unable to  profile name: %s\n",
				    error->message);
			else {
				char *tmp;

				__ns_ldap_err2str(rc, &tmp);
				logit("Error: Unable to  profile name: %s\n",
				    tmp);
			}
			(void) __ns_ldap_freeParam(&paramVal);
			(void) __ns_ldap_freeError(&error);
			return (-1);
		}

		if (paramVal && *paramVal)
			profile = strdup((char *)*paramVal);
		(void) __ns_ldap_freeParam(&paramVal);

		if (profile == NULL) {
			return (-1);
		}

		(void) snprintf(searchfilter, BUFSIZ, _PROFILE_FILTER,
		    _PROFILE1_OBJECTCLASS, _PROFILE2_OBJECTCLASS, profile);

		if ((rc = __ns_ldap_list(_PROFILE_CONTAINER,
		    (const char *)searchfilter, NULL,
		    NULL, NULL, 0,
		    &result, &error, NULL, NULL)) != NS_LDAP_SUCCESS) {

			/*
			 * Is profile name the DEFAULTCONFIGNAME?
			 * syslog Warning, otherwise syslog error.
			 */
			if (strcmp(profile, DEFAULTCONFIGNAME) == 0) {
				syslog(LOG_WARNING,
				    "Ignoring attempt to refresh nonexistent "
				    "default profile: %s.\n",
				    profile);
				logit("Ignoring attempt to refresh nonexistent "
				    "default profile: %s.\n",
				    profile);
			} else if ((error != NULL) &&
			    (error->message != NULL)) {
				syslog(LOG_ERR,
				    "Error: Unable to refresh profile:%s:"
				    " %s\n", profile, error->message);
				logit("Error: Unable to refresh profile:"
				    "%s:%s\n", profile, error->message);
			} else {
				syslog(LOG_ERR, "Error: Unable to refresh "
				    "from profile:%s. (error=%d)\n",
				    profile, rc);
				logit("Error: Unable to refresh from profile "
				    "%s (error=%d)\n", profile, rc);
			}

			(void) __ns_ldap_freeError(&error);
			(void) __ns_ldap_freeResult(&result);
			free(profile);
			return (-1);
		}
		free(profile);


	} while (checkupdate(sighup_update) == TRUE);

	(void) rw_wrlock(&ldap_lock);

	ptr = __ns_ldap_make_config(result);
	(void) __ns_ldap_freeResult(&result);

	if (ptr == NULL) {
		logit("Error: __ns_ldap_make_config failed.\n");
		(void) rw_unlock(&ldap_lock);
		return (-1);
	}

	/*
	 * cross check the config parameters
	 */
	if (__s_api_crosscheck(ptr, errstr, B_TRUE) == NS_SUCCESS) {
		/*
		 * reset the local profile TTL
		 */
		if (ptr->paramList[NS_LDAP_CACHETTL_P].ns_pc)
			current_admin.ldap_stat.ldap_ttl =
			    atol(ptr->paramList[NS_LDAP_CACHETTL_P].ns_pc);

		if (current_admin.debug_level >= DBG_PROFILE_REFRESH) {
			logit("update_from_profile: reset profile TTL to %d"
			    "  seconds\n",
			    current_admin.ldap_stat.ldap_ttl);
			logit("update_from_profile: expire time %ld "
			    "seconds\n",
			    ptr->paramList[NS_LDAP_EXP_P].ns_tm);
		}

		/* set ptr as current_config if the config is changed */
		chg_test_config_change(ptr, change_status);
		rc = 0;
	} else {
		__s_api_destroy_config(ptr);
		logit("Error: downloaded profile failed to pass "
		    "crosscheck (%s).\n", errstr);
		syslog(LOG_ERR, "ldap_cachemgr: %s", errstr);
		rc = -1;
	}
	(void) rw_unlock(&ldap_lock);

	return (rc);
}

int
getldap_init()
{
	ns_ldap_error_t	*error;
	struct timeval	tp;
	ldap_get_chg_cookie_t	cookie;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_init()...\n");
	}

	(void) __ns_ldap_setServer(TRUE);

	(void) rw_wrlock(&ldap_lock);
	if ((error = __ns_ldap_LoadConfiguration()) != NULL) {
		logit("Error: Unable to read '%s': %s\n",
		    NSCONFIGFILE, error->message);
		(void) fprintf(stderr,
		    gettext("\nError: Unable to read '%s': %s\n"),
		    NSCONFIGFILE, error->message);
		__ns_ldap_freeError(&error);
		(void) rw_unlock(&ldap_lock);
		return (-1);
	}
	(void) rw_unlock(&ldap_lock);

	if (gettimeofday(&tp, NULL) == 0) {
		/* statistics: previous refresh time */
		prev_refresh_time = tp.tv_sec;
	}

	/* initialize the data cache */
	(void) getldap_cache_op(CACHE_OP_CREATE,
	    0, NULL, NULL);

	cookie.mgr_pid = getpid();
	cookie.seq_num = 0;
	chg_config_cookie_set(&cookie);
	return (0);
}

static void
perform_update(void)
{
	ns_ldap_error_t	*error = NULL;
	struct timeval	tp;
	char		buf[20];
	int		rc, rc1;
	int		changed = 0;
	void		**paramVal = NULL;
	ns_ldap_self_gssapi_config_t	config;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("perform_update()...\n");
	}

	(void) __ns_ldap_setServer(TRUE);

	if (gettimeofday(&tp, NULL) != 0)
		return;

	rc = __ns_ldap_getParam(NS_LDAP_CACHETTL_P, &paramVal, &error);

	if (rc == NS_LDAP_SUCCESS && paramVal != NULL) {
		current_admin.ldap_stat.ldap_ttl = atol((char *)*paramVal);
	}

	if (error != NULL)
		(void) __ns_ldap_freeError(&error);

	if (paramVal != NULL)
		(void) __ns_ldap_freeParam(&paramVal);

	if (current_admin.debug_level >= DBG_PROFILE_REFRESH) {
		logit("perform_update: current profile TTL is %d seconds\n",
		    current_admin.ldap_stat.ldap_ttl);
	}

	if (current_admin.ldap_stat.ldap_ttl > 0) {
		/*
		 * set the profile TTL parameter, just
		 * in case that the downloading of
		 * the profile from server would fail
		 */

		/*
		 * NS_LDAP_EXP_P is a no op for __ns_ldap_setParam
		 * It depends on NS_LDAP_CACHETTL_P to set it's value
		 * Set NS_LDAP_CACHETTL_P here so NS_LDAP_EXP_P value
		 * can be set.
		 * NS_LDAP_CACHETTL_P value can be reset after the profile is
		 * downloaded from the server, so is NS_LDAP_EXP_P.
		 */
		buf[19] = '\0'; /* null terminated the buffer */
		if (__ns_ldap_setParam(NS_LDAP_CACHETTL_P,
		    lltostr((long long)current_admin.ldap_stat.ldap_ttl,
		    &buf[19]),
		    &error) != NS_LDAP_SUCCESS) {
			logit("Error: __ns_ldap_setParam failed, status: %d "
			    "message: %s\n", error->status, error->message);
			(void)  __ns_ldap_freeError(&error);
			return;
		}

		(void) rw_wrlock(&ldap_lock);
		sighup_update = FALSE;
		(void) rw_unlock(&ldap_lock);

		do {
			rc = update_from_profile(&changed);
			if (rc != 0) {
				logit("Error: Unable to update from profile\n");
			}
		} while (checkupdate(sighup_update) == TRUE);
	} else {
		rc = 0;
	}

	/*
	 * recreate the server info list
	 */
	if (rc == 0) {
		(void) getldap_serverInfo_op(INFO_OP_CREATE, NULL, NULL);

		/* flush the data cache */
		(void) getldap_cache_op(CACHE_OP_DELETE,
		    0, NULL, NULL);

		/* statistics: previous refresh time */
		prev_refresh_time = tp.tv_sec;
	}
	rc1 = __ns_ldap_self_gssapi_config(&config);
	if (rc1 == NS_LDAP_SUCCESS) {
		if (config != NS_LDAP_SELF_GSSAPI_CONFIG_NONE) {
			rc1 = __ns_ldap_check_all_preq(0, 0, 0, config, &error);
			(void)  __ns_ldap_freeError(&error);
			if (rc1 != NS_LDAP_SUCCESS) {
				logit("Error: Check on self credential "
				    "prerquesites failed: %d\n",
				    rc1);
				exit(rc1);
			}
		}
	} else {
		logit("Error: Failed to get self credential configuration %d\n",
		    rc1);
			exit(rc1);
	}

	if (!changed)
		return;

	(void) rw_rdlock(&ldap_lock);
	if (((error = __ns_ldap_DumpConfiguration(NSCONFIGREFRESH)) != NULL) ||
	    ((error = __ns_ldap_DumpConfiguration(NSCREDREFRESH)) != NULL)) {
		logit("Error: __ns_ldap_DumpConfiguration failed, "
		    "status: %d message: %s\n", error->status, error->message);
		__ns_ldap_freeError(&error);
		(void) rw_unlock(&ldap_lock);
		return;
	}
	if (rename(NSCONFIGREFRESH, NSCONFIGFILE) != 0) {
		logit("Error: unlink failed - errno: %s\n", strerror(errno));
		syslog(LOG_ERR, "Unable to refresh profile, LDAP configuration"
		    "files not written");
		(void) rw_unlock(&ldap_lock);
		return;
	}
	if (rename(NSCREDREFRESH, NSCREDFILE) != 0) {
		/*
		 * We probably have inconsistent configuration at this point.
		 * If we were to create a backup file and rename it here, that
		 * operation might also fail. Consequently there is no safe way
		 * to roll back.
		 */
		logit("Error: unlink failed - errno: %s\n", strerror(errno));
		syslog(LOG_ERR, "Unable to refresh profile consistently, "
		    "LDAP configuration files inconsistent");
		(void) rw_unlock(&ldap_lock);
		return;
	}

	(void) rw_unlock(&ldap_lock);
}

void
getldap_refresh()
{
	struct timespec	timeout;
	int		sleeptime;
	struct timeval	tp;
	long		expire = 0;
	void		**paramVal = NULL;
	ns_ldap_error_t	*errorp;
	int		always = 1, err;
	int		first_time = 1;
	int		sig_done = 0;
	int		dbg_level;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_refresh()...\n");
	}

	/*
	 * wait for an available server
	 */
	while (sig_done == 0) {
		(void) mutex_lock(&sig_mutex);
		sig_done = signal_done;
		(void) mutex_unlock(&sig_mutex);
	}

	(void) __ns_ldap_setServer(TRUE);
	while (always) {
		dbg_level = current_admin.debug_level;
		(void) rw_rdlock(&ldap_lock);
		sleeptime = current_admin.ldap_stat.ldap_ttl;
		if (dbg_level >= DBG_PROFILE_REFRESH) {
			logit("getldap_refresh: current profile TTL is %d "
			"seconds\n", current_admin.ldap_stat.ldap_ttl);
		}
		if (gettimeofday(&tp, NULL) == 0) {
			if ((__ns_ldap_getParam(NS_LDAP_EXP_P,
			    &paramVal, &errorp) == NS_LDAP_SUCCESS) &&
			    paramVal != NULL &&
			    (char *)*paramVal != NULL) {
				errno = 0;
				expire = atol((char *)*paramVal);
				(void) __ns_ldap_freeParam(&paramVal);
				if (errno == 0) {
					if (expire == 0) {
						first_time = 0;
						(void) rw_unlock(&ldap_lock);
						(void) cond_init(&cond,
						    NULL, NULL);
						(void) mutex_lock(&sighuplock);
						timeout.tv_sec =
						    CACHESLEEPTIME;
						timeout.tv_nsec = 0;
						if (dbg_level >=
						    DBG_PROFILE_REFRESH) {
							logit("getldap_refresh:"
							    "(1)about to sleep"
							    " for %d seconds\n",
							    CACHESLEEPTIME);
						}
						err = cond_reltimedwait(&cond,
						    &sighuplock, &timeout);
						(void) cond_destroy(&cond);
						(void) mutex_unlock(
						    &sighuplock);
						/*
						 * if woke up by
						 * getldap_revalidate(),
						 * do update right away
						 */
						if (err == ETIME)
							continue;
						else {
							/*
							 * if load
							 * configuration failed
							 * don't do update
							 */
							if (load_config())
								perform_update
								    ();
							continue;
						}
					}
					sleeptime = expire - tp.tv_sec;
					if (dbg_level >= DBG_PROFILE_REFRESH) {
						logit("getldap_refresh: expire "
						    "time = %ld\n", expire);
					}

				}
			}
		}

		(void) rw_unlock(&ldap_lock);

		/*
		 * if this is the first time downloading
		 * the profile or expire time already passed,
		 * do not wait, do update
		 */
		if (first_time == 0 && sleeptime > 0) {
			if (dbg_level >= DBG_PROFILE_REFRESH) {
				logit("getldap_refresh: (2)about to sleep "
				"for %d seconds\n", sleeptime);
			}
			(void) cond_init(&cond, NULL, NULL);
			(void) mutex_lock(&sighuplock);
			timeout.tv_sec = sleeptime;
			timeout.tv_nsec = 0;
			err = cond_reltimedwait(&cond,
			    &sighuplock, &timeout);
			(void) cond_destroy(&cond);
			(void) mutex_unlock(&sighuplock);
		}
		/*
		 * if load concfiguration failed
		 * don't do update
		 */
		if (load_config())
			perform_update();
		first_time = 0;
	}
}

void
getldap_revalidate()
{
	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_revalidate()...\n");
	}
	/* block signal SIGHUP */
	(void) sighold(SIGHUP);

	/* now awake the sleeping refresh thread */
	(void) cond_signal(&cond);

	/* release signal SIGHUP */
	(void) sigrelse(SIGHUP);

}

void
getldap_admincred(LineBuf *config_info, ldap_call_t *in)
{
	ns_ldap_error_t	*error;
	ldap_config_out_t *cout;
	ucred_t *uc = NULL;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_admincred()...\n");
	}
	/* check privileges */
	if (is_root_or_all_privs("GETADMINCRED", &uc) == 0) {
		logit("admin credential requested by a non-root and no ALL "
		    "privilege user not allowed");
		config_info->str = NULL;
		config_info->len = 0;
	} else {
		(void) rw_rdlock(&ldap_lock);
		if ((error = __ns_ldap_LoadDoorInfo(config_info,
		    in->ldap_u.domainname, NULL, 1)) != NULL) {
			if (error != NULL && error->message != NULL)
				logit("Error: ldap_lookup: %s\n",
				    error->message);
			(void) __ns_ldap_freeError(&error);

			config_info->str = NULL;
			config_info->len = 0;
		}
		/* set change cookie */
		cout = (ldap_config_out_t *)config_info->str;
		if (cout)
			cout->cookie = chg_config_cookie_get();
		(void) rw_unlock(&ldap_lock);
	}
}

void
getldap_lookup(LineBuf *config_info, ldap_call_t *in)
{
	ns_ldap_error_t	*error;
	ldap_config_out_t *cout;

	if (current_admin.debug_level >= DBG_ALL) {
		logit("getldap_lookup()...\n");
	}
	(void) rw_rdlock(&ldap_lock);
	if ((error = __ns_ldap_LoadDoorInfo(config_info,
	    in->ldap_u.domainname, NULL, 0)) != NULL) {
		if (error != NULL && error->message != NULL)
			logit("Error: ldap_lookup: %s\n", error->message);
		(void) __ns_ldap_freeError(&error);

		config_info->str = NULL;
		config_info->len = 0;
	}
	/* set change cookie */
	cout = (ldap_config_out_t *)config_info->str;
	if (cout)
		cout->cookie = chg_config_cookie_get();
	(void) rw_unlock(&ldap_lock);
}
/*
 * It creates the header and data stream to be door returned and notify
 * chg_get_statusChange() threads.
 * This is called after all getldap_get_rootDSE() threads are joined.
 */
void
test_server_change(server_info_t *head)
{
	server_info_t *info;
	int	len = 0, num = 0, ds_len = 0, new_len = 0, tlen = 0;
	char	*tmp_buf = NULL, *ptr = NULL, *status = NULL;
	ldap_get_change_out_t *cout;

	ds_len = strlen(DOORLINESEP);

	for (info = head; info; info = info->next) {
		(void) mutex_lock(&info->mutex[0]);
		if (info->sinfo[0].change != 0) {
			/* "9.9.9.9|NS_SERVER_CHANGE_UP|" */
			len += 2 * ds_len + strlen(info->sinfo[0].addr) +
			    strlen(NS_SERVER_CHANGE_UP);
			num++;
		}
		(void) mutex_unlock(&info->mutex[0]);
	}

	if (len == 0)
		return;

	len++; /* '\0' */

	tlen = sizeof (ldap_get_change_out_t) - sizeof (int) + len;
	if ((tmp_buf = malloc(tlen)) == NULL)
		return;

	cout = (ldap_get_change_out_t *)tmp_buf;
	cout->type = NS_STATUS_CHANGE_TYPE_SERVER;
	/* cout->cookie is set by chg_notify_statusChange */
	cout->server_count = num;
	cout->data_size = len;

	/* Create IP|UP or DOWN|IP|UP or DOWN| ... */
	ptr = cout->data;
	new_len = len;
	for (info = head; info; info = info->next) {
		(void) mutex_lock(&info->mutex[0]);
		if (info->sinfo[0].change == 0) {
			(void) mutex_unlock(&info->mutex[0]);
			continue;
		}

		if (info->sinfo[0].change == NS_SERVER_UP)
			status = NS_SERVER_CHANGE_UP;
		else if (info->sinfo[0].change == NS_SERVER_DOWN)
			status = NS_SERVER_CHANGE_DOWN;
		else {
			syslog(LOG_WARNING, gettext("Bad change value %d"),
			    info->sinfo[0].change);
			(void) mutex_unlock(&info->mutex[0]);
			free(tmp_buf);
			return;
		}

		if ((snprintf(ptr, new_len, "%s%s%s%s",
		    info->sinfo[0].addr, DOORLINESEP,
		    status, DOORLINESEP)) >= new_len) {
			(void) mutex_unlock(&info->mutex[0]);
			break;
		}
		new_len -= strlen(ptr);
		ptr += strlen(ptr);

		(void) mutex_unlock(&info->mutex[0]);
	}
	(void) chg_notify_statusChange(tmp_buf);
}
/*
 * It creates the header and data stream to be door returned and notify
 * chg_get_statusChange() threads.
 * This is called in removing server case.
 */
static void
create_buf_and_notify(char *input, ns_server_status_t st)
{
	rm_svr_t *rms = (rm_svr_t *)input;
	char	*tmp_buf, *ptr, *status;
	int	len, tlen;
	ldap_get_change_out_t *cout;

	/* IP|UP or DOWN| */
	len = 2 * strlen(DOORLINESEP) + strlen(rms->addr) +
	    strlen(NS_SERVER_CHANGE_UP) + 1;

	tlen = sizeof (ldap_get_change_out_t) - sizeof (int) + len;

	if ((tmp_buf = malloc(tlen)) == NULL)
		return;

	cout = (ldap_get_change_out_t *)tmp_buf;
	cout->type = NS_STATUS_CHANGE_TYPE_SERVER;
	/* cout->cookie is set by chg_notify_statusChange */
	cout->server_count = 1;
	cout->data_size = len;

	/* Create IP|DOWN| */
	ptr = cout->data;
	if (st == NS_SERVER_UP)
		status = NS_SERVER_CHANGE_UP;
	else if (st == NS_SERVER_DOWN)
		status = NS_SERVER_CHANGE_DOWN;

	(void) snprintf(ptr, len, "%s%s%s%s",
	    rms->addr, DOORLINESEP, status, DOORLINESEP);

	(void) chg_notify_statusChange(tmp_buf);

}

/*
 * Return: 0 server is down, 1 server is up
 */
static int
contact_server(char *addr)
{
	char		*rootDSE = NULL;
	ns_ldap_error_t	*error = NULL;
	int		rc;

	if (__ns_ldap_getRootDSE(addr, &rootDSE, &error,
	    SA_ALLOW_FALLBACK) != NS_LDAP_SUCCESS) {
		if (current_admin.debug_level >= DBG_ALL)
			logit("get rootDSE %s failed. %s", addr,
			    error->message ? error->message : "");
		rc = 0;
	} else
		rc = 1;

	if (rootDSE)
		free(rootDSE);
	if (error)
		(void) __ns_ldap_freeError(&error);

	return (rc);
}

/*
 * The thread is spawned to do contact_server() so it won't be blocking
 * getldap_serverInfo_op(INFO_OP_GETSERVER, ...) case.
 * After contact_server() is done, it calls
 * getldap_serverInfo_op(INFO_OP_REMOVESERVER, ...) to return to the remaining
 * program flow. It's meant to maintain the original program flow yet be
 * non-blocking when it's contacting server.
 */
static void *
remove_server_thread(void *arg)
{
	char *addr = (char *)arg, *out = NULL;
	int up;
	rm_svr_t rms;

	up = contact_server(addr);

	rms.addr = addr;
	rms.up = up;

	(void) getldap_serverInfo_op(INFO_OP_REMOVESERVER, (char *)&rms, &out);

	free(addr);

	thr_exit(NULL);
	return (NULL);
}
/*
 * addr is allocated and is freed by remove_server_thread
 * It starts a thread to contact server and remove server to avoid long wait
 * or recursion.
 */
static void
remove_server(char *addr)
{
	if (thr_create(NULL, 0, remove_server_thread,
	    (void *)addr, THR_BOUND|THR_DETACHED, NULL) != 0) {
		free(addr);
		syslog(LOG_ERR, "thr_create failed for remove_server_thread");
	}
}
/*
 * Compare the server_status and mark it up or down accordingly.
 * This is called in removing server case.
 */
static ns_server_status_t
set_server_status(char *input, server_info_t *head)
{
	rm_svr_t *rms = (rm_svr_t *)input;
	ns_server_status_t changed = 0;
	server_info_t *info;

	for (info = head; info != NULL; info = info->next) {
		(void) mutex_lock(&info->mutex[0]);
		if (strcmp(info->sinfo[0].addr, rms->addr) == 0) {
			if (info->sinfo[0].server_status == INFO_SERVER_UP &&
			    rms->up == FALSE) {
				info->sinfo[0].prev_server_status =
				    info->sinfo[0].server_status;
				info->sinfo[0].server_status =
				    INFO_SERVER_ERROR;
				info->sinfo[0].change = NS_SERVER_DOWN;
				changed = NS_SERVER_DOWN;

			} else if (info->sinfo[0].server_status ==
			    INFO_SERVER_ERROR && rms->up == TRUE) {
				/*
				 * It should be INFO_SERVER_UP, but check here
				 */
				info->sinfo[0].prev_server_status =
				    info->sinfo[0].server_status;
				info->sinfo[0].server_status =
				    INFO_SERVER_UP;
				info->sinfo[0].change = NS_SERVER_UP;
				changed = NS_SERVER_UP;
			}
			(void) mutex_unlock(&info->mutex[0]);
			break;
		}
		(void) mutex_unlock(&info->mutex[0]);
	}
	if (changed) {
		/* ldap_cachemgr -g option looks up [1] */
		(void) mutex_lock(&info->mutex[1]);
		info->sinfo[1].prev_server_status =
		    info->sinfo[1].server_status;
		if (changed == NS_SERVER_DOWN)
			info->sinfo[1].server_status = INFO_SERVER_ERROR;
		else if (changed == NS_SERVER_UP)
			info->sinfo[1].server_status = INFO_SERVER_UP;
		(void) mutex_unlock(&info->mutex[1]);
	}
	return (changed);
}