root/usr/src/cmd/ldapcachemgr/cachemgr_getldap.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
 *
 * Copyright 2018 Joyent, Inc.
 */

#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(void)
{
        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;
        char            errmsg[MAXERROR];
        ns_ldap_return_code     rc;
        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 (rc = __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;
                if (error && error->message) {
                        serverInfo->sinfo[1].errormsg = strdup(error->message);
                } else {
                        (void) snprintf(errmsg, sizeof (errmsg), "%s %s "
                            "(rc = %d)", gettext("Can not get the root DSE from"
                            " server"), serverInfo->sinfo[1].addr, rc);
                        serverInfo->sinfo[1].errormsg = strdup(errmsg);
                }

                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;

        (void) pthread_setname_np(pthread_self(), "getldap_serverinfo");

        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, USYNC_THREAD, 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, 0, 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;

        (void) pthread_setname_np(pthread_self(), "getldap_refresh");

        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,
                                                    USYNC_THREAD, 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, USYNC_THREAD, 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(int signal __unused)
{
        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;

        (void) pthread_setname_np(pthread_self(), "remove_server");

        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);
}