OpenSolaris_b135/lib/libsldap/common/ns_connect.c

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

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

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <synch.h>
#include <time.h>
#include <libintl.h>
#include <thread.h>
#include <syslog.h>
#include <sys/mman.h>
#include <nsswitch.h>
#include <nss_dbdefs.h>
#include "solaris-priv.h"
#include "solaris-int.h"
#include "ns_sldap.h"
#include "ns_internal.h"
#include "ns_cache_door.h"
#include "ns_connmgmt.h"
#include "ldappr.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <procfs.h>
#include <unistd.h>

#define	USE_DEFAULT_PORT 0

static ns_ldap_return_code performBind(const ns_cred_t *,
					LDAP *,
					int,
					ns_ldap_error_t **,
					int,
					int);
static ns_ldap_return_code createSession(const ns_cred_t *,
					const char *,
					uint16_t,
					int,
					LDAP **,
					ns_ldap_error_t **);

extern int ldap_sasl_cram_md5_bind_s(LDAP *, char *, struct berval *,
		LDAPControl **, LDAPControl **);
extern int ldapssl_install_gethostbyaddr(LDAP *ld, const char *skip);

extern int __door_getconf(char **buffer, int *buflen,
		ns_ldap_error_t **error, int callnumber);
extern int __ns_ldap_freeUnixCred(UnixCred_t **credp);
extern int SetDoorInfoToUnixCred(char *buffer,
		ns_ldap_error_t **errorp,
		UnixCred_t **cred);

static int openConnection(LDAP **, const char *, const ns_cred_t *,
		int, ns_ldap_error_t **, int, int, ns_conn_user_t *);
static void
_DropConnection(ConnectionID cID, int flag, int fini);

static mutex_t sessionPoolLock = DEFAULTMUTEX;

static Connection **sessionPool = NULL;
static int sessionPoolSize = 0;

/*
 * SSF values are for SASL integrity & privacy.
 * JES DS5.2 does not support this feature but DS6 does.
 * The values between 0 and 65535 can work with both server versions.
 */
#define	MAX_SASL_SSF	65535
#define	MIN_SASL_SSF	0

/* Number of hostnames to allocate memory for */
#define	NUMTOMALLOC	32

/*
 * This function get the servers from the lists and returns
 * the first server with the empty lists of server controls and
 * SASL mechanisms. It is invoked if it is not possible to obtain a server
 * from ldap_cachemgr or the local list.
 */
static
ns_ldap_return_code
getFirstFromConfig(ns_server_info_t *ret, ns_ldap_error_t **error)
{
	char			**servers = NULL;
	ns_ldap_return_code	ret_code;
	char			errstr[MAXERROR];

	/* get first server from config list unavailable otherwise */
	ret_code = __s_api_getServers(&servers, error);
	if (ret_code != NS_LDAP_SUCCESS) {
		if (servers != NULL) {
			__s_api_free2dArray(servers);
		}
		return (ret_code);
	}

	if (servers == NULL || servers[0] == NULL) {
		__s_api_free2dArray(servers);
		(void) sprintf(errstr,
		    gettext("No server found in configuration"));
		MKERROR(LOG_ERR, *error, NS_CONFIG_NODEFAULT,
		    strdup(errstr), NS_LDAP_MEMORY);
		return (NS_LDAP_CONFIG);
	}

	ret->server = strdup(servers[0]);
	if (ret->server == NULL) {
		__s_api_free2dArray(servers);
		return (NS_LDAP_MEMORY);
	}

	ret->saslMechanisms = NULL;
	ret->controls = NULL;

	__s_api_free2dArray(servers);

	return (NS_LDAP_SUCCESS);
}

/* very similar to __door_getldapconfig() in ns_config.c */
static int
__door_getadmincred(char **buffer, int *buflen, ns_ldap_error_t **error)
{
	return (__door_getconf(buffer, buflen, error, GETADMINCRED));
}

/*
 * This function requests Admin credentials from the cache manager through
 * the door functionality
 */

static int
requestAdminCred(UnixCred_t **cred, ns_ldap_error_t **error)
{
	char	*buffer = NULL;
	int	buflen = 0;
	int	ret;

	*error = NULL;
	ret = __door_getadmincred(&buffer, &buflen, error);

	if (ret != NS_LDAP_SUCCESS) {
		if (*error != NULL && (*error)->message != NULL)
			syslog(LOG_WARNING, "libsldap: %s", (*error)->message);
		return (ret);
	}

	/* now convert from door format */
	ret = SetDoorInfoToUnixCred(buffer, error, cred);
	free(buffer);

	return (ret);
}

/*
 * This function requests a server from the cache manager through
 * the door functionality
 */

int
__s_api_requestServer(const char *request, const char *server,
	ns_server_info_t *ret, ns_ldap_error_t **error,  const char *addrType)
{
	union {
		ldap_data_t	s_d;
		char		s_b[DOORBUFFERSIZE];
	} space;
	ldap_data_t		*sptr;
	int			ndata;
	int			adata;
	char			errstr[MAXERROR];
	const char		*ireq;
	char			*rbuf, *ptr, *rest;
	char			*dptr;
	char			**mptr, **mptr1, **cptr, **cptr1;
	int			mcnt, ccnt;
	int			len;
	ns_ldap_return_code	ret_code;

	if (ret == NULL || error == NULL) {
		return (NS_LDAP_OP_FAILED);
	}
	(void) memset(ret, 0, sizeof (ns_server_info_t));
	*error = NULL;

	if (request == NULL)
		ireq = NS_CACHE_NEW;
	else
		ireq = request;

	/*
	 * In the 'Standalone' mode a server will be obtained
	 * from the local libsldap's list
	 */
	if (__s_api_isStandalone()) {
		if ((ret_code = __s_api_findRootDSE(ireq,
		    server,
		    addrType,
		    ret,
		    error)) != NS_LDAP_SUCCESS) {
			/*
			 * get first server from local list only once
			 * to prevent looping
			 */
			if (strcmp(ireq, NS_CACHE_NEW) != 0)
				return (ret_code);

			syslog(LOG_WARNING,
			    "libsldap (\"standalone\" mode): "
			    "can not find any available server. "
			    "Return the first one from the lists");
			if (*error != NULL) {
				(void) __ns_ldap_freeError(error);
			}

			ret_code = getFirstFromConfig(ret, error);
			if (ret_code != NS_LDAP_SUCCESS) {
				return (ret_code);
			}

			if (strcmp(addrType, NS_CACHE_ADDR_HOSTNAME) == 0) {
				ret_code = __s_api_ip2hostname(ret->server,
				    &ret->serverFQDN);
				if (ret_code != NS_LDAP_SUCCESS) {
					(void) snprintf(errstr,
					    sizeof (errstr),
					    gettext("The %s address "
					    "can not be resolved into "
					    "a host name. Returning "
					    "the address as it is."),
					    ret->server);
					MKERROR(LOG_ERR,
					    *error,
					    NS_CONFIG_NOTLOADED,
					    strdup(errstr),
					    NS_LDAP_MEMORY);
					free(ret->server);
					ret->server = NULL;
					return (NS_LDAP_INTERNAL);
				}
			}
		}

		return (NS_LDAP_SUCCESS);
	}

	(void) memset(space.s_b, 0, DOORBUFFERSIZE);

	adata = (sizeof (ldap_call_t) + strlen(ireq) + strlen(addrType) + 1);
	if (server != NULL) {
		adata += strlen(DOORLINESEP) + 1;
		adata += strlen(server) + 1;
	}
	ndata = sizeof (space);
	len = sizeof (space) - sizeof (space.s_d.ldap_call.ldap_callnumber);
	space.s_d.ldap_call.ldap_callnumber = GETLDAPSERVER;
	if (strlcpy(space.s_d.ldap_call.ldap_u.domainname, ireq, len) >= len)
		return (NS_LDAP_MEMORY);
	if (strlcat(space.s_d.ldap_call.ldap_u.domainname, addrType, len) >=
	    len)
		return (NS_LDAP_MEMORY);
	if (server != NULL) {
		if (strlcat(space.s_d.ldap_call.ldap_u.domainname,
		    DOORLINESEP, len) >= len)
			return (NS_LDAP_MEMORY);
		if (strlcat(space.s_d.ldap_call.ldap_u.domainname, server,
		    len) >= len)
			return (NS_LDAP_MEMORY);
	}
	sptr = &space.s_d;

	switch (__ns_ldap_trydoorcall(&sptr, &ndata, &adata)) {
	case NS_CACHE_SUCCESS:
		break;
	/* this case is for when the $mgr is not running, but ldapclient */
	/* is trying to initialize things */
	case NS_CACHE_NOSERVER:
		ret_code = getFirstFromConfig(ret, error);
		if (ret_code != NS_LDAP_SUCCESS) {
			return (ret_code);
		}

		if (strcmp(addrType, NS_CACHE_ADDR_HOSTNAME) == 0) {
			ret_code = __s_api_ip2hostname(ret->server,
			    &ret->serverFQDN);
			if (ret_code != NS_LDAP_SUCCESS) {
				(void) snprintf(errstr,
				    sizeof (errstr),
				    gettext("The %s address "
				    "can not be resolved into "
				    "a host name. Returning "
				    "the address as it is."),
				    ret->server);
				MKERROR(LOG_ERR,
				    *error,
				    NS_CONFIG_NOTLOADED,
				    strdup(errstr),
				    NS_LDAP_MEMORY);
				free(ret->server);
				ret->server = NULL;
				return (NS_LDAP_INTERNAL);
			}
		}
		return (NS_LDAP_SUCCESS);
	case NS_CACHE_NOTFOUND:
	default:
		return (NS_LDAP_OP_FAILED);
	}

	/* copy info from door call return structure here */
	rbuf =  space.s_d.ldap_ret.ldap_u.config;

	/* Get the host */
	ptr = strtok_r(rbuf, DOORLINESEP, &rest);
	if (ptr == NULL) {
		(void) sprintf(errstr, gettext("No server returned from "
		    "ldap_cachemgr"));
		MKERROR(LOG_WARNING, *error, NS_CONFIG_CACHEMGR,
		    strdup(errstr), NS_LDAP_MEMORY);
		return (NS_LDAP_OP_FAILED);
	}
	ret->server = strdup(ptr);
	if (ret->server == NULL) {
		return (NS_LDAP_MEMORY);
	}
	/* Get the host FQDN format */
	if (strcmp(addrType, NS_CACHE_ADDR_HOSTNAME) == 0) {
		ptr = strtok_r(NULL, DOORLINESEP, &rest);
		if (ptr == NULL) {
			(void) sprintf(errstr, gettext("No server FQDN format "
			    "returned from ldap_cachemgr"));
			MKERROR(LOG_WARNING, *error, NS_CONFIG_CACHEMGR,
			    strdup(errstr), NULL);
			free(ret->server);
			ret->server = NULL;
			return (NS_LDAP_OP_FAILED);
		}
		ret->serverFQDN = strdup(ptr);
		if (ret->serverFQDN == NULL) {
			free(ret->server);
			ret->server = NULL;
			return (NS_LDAP_MEMORY);
		}
	}

	/* get the Supported Controls/SASL mechs */
	mptr = NULL;
	mcnt = 0;
	cptr = NULL;
	ccnt = 0;
	for (;;) {
		ptr = strtok_r(NULL, DOORLINESEP, &rest);
		if (ptr == NULL)
			break;
		if (strncasecmp(ptr, _SASLMECHANISM,
		    _SASLMECHANISM_LEN) == 0) {
			dptr = strchr(ptr, '=');
			if (dptr == NULL)
				continue;
			dptr++;
			mptr1 = (char **)realloc((void *)mptr,
			    sizeof (char *) * (mcnt+2));
			if (mptr1 == NULL) {
				__s_api_free2dArray(mptr);
				if (sptr != &space.s_d) {
					(void) munmap((char *)sptr, ndata);
				}
				__s_api_free2dArray(cptr);
				__s_api_free_server_info(ret);
				return (NS_LDAP_MEMORY);
			}
			mptr = mptr1;
			mptr[mcnt] = strdup(dptr);
			if (mptr[mcnt] == NULL) {
				if (sptr != &space.s_d) {
					(void) munmap((char *)sptr, ndata);
				}
				__s_api_free2dArray(cptr);
				cptr = NULL;
				__s_api_free2dArray(mptr);
				mptr = NULL;
				__s_api_free_server_info(ret);
				return (NS_LDAP_MEMORY);
			}
			mcnt++;
			mptr[mcnt] = NULL;
		}
		if (strncasecmp(ptr, _SUPPORTEDCONTROL,
		    _SUPPORTEDCONTROL_LEN) == 0) {
			dptr = strchr(ptr, '=');
			if (dptr == NULL)
				continue;
			dptr++;
			cptr1 = (char **)realloc((void *)cptr,
			    sizeof (char *) * (ccnt+2));
			if (cptr1 == NULL) {
				if (sptr != &space.s_d) {
					(void) munmap((char *)sptr, ndata);
				}
				__s_api_free2dArray(cptr);
				__s_api_free2dArray(mptr);
				mptr = NULL;
				__s_api_free_server_info(ret);
				return (NS_LDAP_MEMORY);
			}
			cptr = cptr1;
			cptr[ccnt] = strdup(dptr);
			if (cptr[ccnt] == NULL) {
				if (sptr != &space.s_d) {
					(void) munmap((char *)sptr, ndata);
				}
				__s_api_free2dArray(cptr);
				cptr = NULL;
				__s_api_free2dArray(mptr);
				mptr = NULL;
				__s_api_free_server_info(ret);
				return (NS_LDAP_MEMORY);
			}
			ccnt++;
			cptr[ccnt] = NULL;
		}
	}
	if (mptr != NULL) {
		ret->saslMechanisms = mptr;
	}
	if (cptr != NULL) {
		ret->controls = cptr;
	}


	/* clean up door call */
	if (sptr != &space.s_d) {
		(void) munmap((char *)sptr, ndata);
	}
	*error = NULL;

	return (NS_LDAP_SUCCESS);
}


#ifdef DEBUG
/*
 * printCred(): prints the credential structure
 */
static void
printCred(FILE *fp, const ns_cred_t *cred)
{
	thread_t	t = thr_self();

	if (cred == NULL) {
		(void) fprintf(fp, "tid= %d: printCred: cred is NULL\n", t);
		return;
	}

	(void) fprintf(fp, "tid= %d: AuthType=%d", t, cred->auth.type);
	(void) fprintf(fp, "tid= %d: TlsType=%d", t, cred->auth.tlstype);
	(void) fprintf(fp, "tid= %d: SaslMech=%d", t, cred->auth.saslmech);
	(void) fprintf(fp, "tid= %d: SaslOpt=%d", t, cred->auth.saslopt);
	if (cred->hostcertpath)
		(void) fprintf(fp, "tid= %d: hostCertPath=%s\n",
		    t, cred->hostcertpath);
	if (cred->cred.unix_cred.userID)
		(void) fprintf(fp, "tid= %d: userID=%s\n",
		    t, cred->cred.unix_cred.userID);
	if (cred->cred.unix_cred.passwd)
		(void) fprintf(fp, "tid= %d: passwd=%s\n",
		    t, cred->cred.unix_cred.passwd);
}

/*
 * printConnection(): prints the connection structure
 */
static void
printConnection(FILE *fp, Connection *con)
{
	thread_t	t = thr_self();

	if (con == NULL)
		return;

	(void) fprintf(fp, "tid= %d: connectionID=%d\n", t, con->connectionId);
	(void) fprintf(fp, "tid= %d: usedBit=%d\n", t, con->usedBit);
	(void) fprintf(fp, "tid= %d: threadID=%d\n", t, con->threadID);
	if (con->serverAddr) {
		(void) fprintf(fp, "tid= %d: serverAddr=%s\n",
		    t, con->serverAddr);
	}
	printCred(fp, con->auth);
}
#endif

/*
 * addConnection(): inserts a connection in the connection list.
 * It will also sets use bit and the thread Id for the thread
 * using the connection for the first time.
 * Returns: -1 = failure, new Connection ID = success
 */
static int
addConnection(Connection *con)
{
	int i;

	if (!con)
		return (-1);
#ifdef DEBUG
	(void) fprintf(stderr, "Adding connection thrid=%d\n", con->threadID);
#endif /* DEBUG */
	(void) mutex_lock(&sessionPoolLock);
	if (sessionPool == NULL) {
		sessionPoolSize = SESSION_CACHE_INC;
		sessionPool = calloc(sessionPoolSize,
		    sizeof (Connection *));
		if (!sessionPool) {
			(void) mutex_unlock(&sessionPoolLock);
			return (-1);
		}
#ifdef DEBUG
		(void) fprintf(stderr, "Initialized sessionPool\n");
#endif /* DEBUG */
	}
	for (i = 0; (i < sessionPoolSize) && (sessionPool[i] != NULL); ++i)
		;
	if (i == sessionPoolSize) {
		/* run out of array, need to increase sessionPool */
		Connection **cl;
		cl = (Connection **) realloc(sessionPool,
		    (sessionPoolSize + SESSION_CACHE_INC) *
		    sizeof (Connection *));
		if (!cl) {
			(void) mutex_unlock(&sessionPoolLock);
			return (-1);
		}
		(void) memset(cl + sessionPoolSize, 0,
		    SESSION_CACHE_INC * sizeof (Connection *));
		sessionPool = cl;
		sessionPoolSize += SESSION_CACHE_INC;
#ifdef DEBUG
		(void) fprintf(stderr, "Increased sessionPoolSize to: %d\n",
		    sessionPoolSize);
#endif /* DEBUG */
	}
	sessionPool[i] = con;
	con->usedBit = B_TRUE;
	(void) mutex_unlock(&sessionPoolLock);
	con->connectionId = i + CONID_OFFSET;
#ifdef DEBUG
	(void) fprintf(stderr, "Connection added [%d]\n", i);
	printConnection(stderr, con);
#endif /* DEBUG */
	return (i + CONID_OFFSET);
}

/*
 * findConnection(): find an available connection from the list
 * that matches the criteria specified in Connection structure.
 * If serverAddr is NULL, then find a connection to any server
 * as long as it matches the rest of the parameters.
 * Returns: -1 = failure, the Connection ID found = success.
 */
static int
findConnection(int flags, const char *serverAddr,
	const ns_cred_t *auth, Connection **conp)
{
	Connection *cp;
	int i;
#ifdef DEBUG
	thread_t t;
#endif /* DEBUG */

	if (auth == NULL || conp == NULL)
		return (-1);
	*conp = NULL;

	/*
	 * If a new connection is requested, no need to continue.
	 * If the process is not nscd and is not requesting keep
	 * connections alive, no need to continue.
	 */
	if ((flags & NS_LDAP_NEW_CONN) || (!__s_api_nscd_proc() &&
	    !__s_api_peruser_proc() && !(flags & NS_LDAP_KEEP_CONN)))
		return (-1);

#ifdef DEBUG
	t = thr_self();
	(void) fprintf(stderr, "tid= %d: Find connection\n", t);
	(void) fprintf(stderr, "tid= %d: Looking for ....\n", t);
	if (serverAddr && *serverAddr)
		(void) fprintf(stderr, "tid= %d: serverAddr=%s\n",
		    t, serverAddr);
	else
		(void) fprintf(stderr, "tid= %d: serverAddr=NULL\n", t);
	printCred(stderr, auth);
	fflush(stderr);
#endif /* DEBUG */
	if (sessionPool == NULL)
		return (-1);
	(void) mutex_lock(&sessionPoolLock);
	for (i = 0; i < sessionPoolSize; ++i) {
		if (sessionPool[i] == NULL)
			continue;
		cp = sessionPool[i];
#ifdef DEBUG
		(void) fprintf(stderr,
		    "tid: %d: checking connection [%d] ....\n", t, i);
		printConnection(stderr, cp);
#endif /* DEBUG */
		if ((cp->usedBit) || (serverAddr && *serverAddr &&
		    (strcasecmp(serverAddr, cp->serverAddr) != 0)))
			continue;

		if (__s_api_is_auth_matched(cp->auth, auth) == B_FALSE)
			continue;

		/* found an available connection */
		cp->usedBit = B_TRUE;
		(void) mutex_unlock(&sessionPoolLock);
		cp->threadID = thr_self();
		*conp = cp;
#ifdef DEBUG
		(void) fprintf(stderr,
		    "tid %d: Connection found cID=%d\n", t, i);
		fflush(stderr);
#endif /* DEBUG */
		return (i + CONID_OFFSET);
	}
	(void) mutex_unlock(&sessionPoolLock);
	return (-1);
}

/*
 * Free a Connection structure
 */
void
__s_api_freeConnection(Connection *con)
{
	if (con == NULL)
		return;
	if (con->serverAddr)
		free(con->serverAddr);
	if (con->auth)
		(void) __ns_ldap_freeCred(&(con->auth));
	if (con->saslMechanisms) {
		__s_api_free2dArray(con->saslMechanisms);
	}
	if (con->controls) {
		__s_api_free2dArray(con->controls);
	}
	free(con);
}

/*
 * Find a connection matching the passed in criteria.  If an open
 * connection with that criteria exists use it, otherwise open a
 * new connection.
 * Success: returns the pointer to the Connection structure
 * Failure: returns NULL, error code and message should be in errorp
 */

static int
makeConnection(Connection **conp, const char *serverAddr,
	const ns_cred_t *auth, ConnectionID *cID, int timeoutSec,
	ns_ldap_error_t **errorp, int fail_if_new_pwd_reqd,
	int nopasswd_acct_mgmt, int flags, char ***badsrvrs,
	ns_conn_user_t *conn_user)
{
	Connection *con = NULL;
	ConnectionID id;
	char errmsg[MAXERROR];
	int rc, exit_rc = NS_LDAP_SUCCESS;
	ns_server_info_t sinfo;
	char *hReq, *host = NULL;
	LDAP *ld = NULL;
	int passwd_mgmt = 0;
	int totalbad = 0; /* Number of servers contacted unsuccessfully */
	short	memerr = 0; /* Variable for tracking memory allocation */
	char *serverAddrType = NULL, **bindHost = NULL;


	if (conp == NULL || errorp == NULL || auth == NULL)
		return (NS_LDAP_INVALID_PARAM);
	*errorp = NULL;
	*conp = NULL;
	(void) memset(&sinfo, 0, sizeof (sinfo));

	if ((id = findConnection(flags, serverAddr, auth, &con)) != -1) {
		/* connection found in cache */
#ifdef DEBUG
		(void) fprintf(stderr, "tid= %d: connection found in "
		    "cache %d\n", thr_self(), id);
		fflush(stderr);
#endif /* DEBUG */
		*cID = id;
		*conp = con;
		return (NS_LDAP_SUCCESS);
	}

	if (auth->auth.saslmech == NS_LDAP_SASL_GSSAPI) {
		serverAddrType = NS_CACHE_ADDR_HOSTNAME;
		bindHost = &sinfo.serverFQDN;
	} else {
		serverAddrType = NS_CACHE_ADDR_IP;
		bindHost = &sinfo.server;
	}

	if (serverAddr) {
		if (__s_api_isInitializing()) {
			/*
			 * When obtaining the root DSE, connect to the server
			 * passed here through the serverAddr parameter
			 */
			sinfo.server = strdup(serverAddr);
			if (sinfo.server == NULL)
				return (NS_LDAP_MEMORY);
			if (strcmp(serverAddrType,
			    NS_CACHE_ADDR_HOSTNAME) == 0) {
				rc = __s_api_ip2hostname(sinfo.server,
				    &sinfo.serverFQDN);
				if (rc != NS_LDAP_SUCCESS) {
					(void) snprintf(errmsg,
					    sizeof (errmsg),
					    gettext("The %s address "
					    "can not be resolved into "
					    "a host name. Returning "
					    "the address as it is."),
					    serverAddr);
					MKERROR(LOG_ERR,
					    *errorp,
					    NS_CONFIG_NOTLOADED,
					    strdup(errmsg),
					    NS_LDAP_MEMORY);
					__s_api_free_server_info(&sinfo);
					return (NS_LDAP_INTERNAL);
				}
			}
		} else {
			/*
			 * We're given the server address, just use it.
			 * In case of sasl/GSSAPI, serverAddr would need
			 * to be a FQDN.  We assume this is the case for now.
			 *
			 * Only the server address fields of sinfo structure
			 * are filled in since these are the only relevant
			 * data that we have. Other fields of this structure
			 * (controls, saslMechanisms) are kept to NULL.
			 */
			sinfo.server = strdup(serverAddr);
			if (sinfo.server == NULL)  {
				return (NS_LDAP_MEMORY);
			}
			if (auth->auth.saslmech == NS_LDAP_SASL_GSSAPI) {
				sinfo.serverFQDN = strdup(serverAddr);
				if (sinfo.serverFQDN == NULL) {
					free(sinfo.server);
					return (NS_LDAP_MEMORY);
				}
			}
		}
		rc = openConnection(&ld, *bindHost, auth, timeoutSec, errorp,
		    fail_if_new_pwd_reqd, passwd_mgmt, conn_user);
		if (rc == NS_LDAP_SUCCESS || rc ==
		    NS_LDAP_SUCCESS_WITH_INFO) {
			exit_rc = rc;
			goto create_con;
		} else {
			if (auth->auth.saslmech == NS_LDAP_SASL_GSSAPI) {
				(void) snprintf(errmsg, sizeof (errmsg),
				    "%s %s", gettext("makeConnection: "
				    "failed to open connection using "
				    "sasl/GSSAPI to"), *bindHost);
			} else {
				(void) snprintf(errmsg, sizeof (errmsg),
				    "%s %s", gettext("makeConnection: "
				    "failed to open connection to"),
				    *bindHost);
			}
			syslog(LOG_ERR, "libsldap: %s", errmsg);
			__s_api_free_server_info(&sinfo);
			return (rc);
		}
	}

	/* No cached connection, create one */
	for (; ; ) {
		if (host == NULL)
			hReq = NS_CACHE_NEW;
		else
			hReq = NS_CACHE_NEXT;
		rc = __s_api_requestServer(hReq, host, &sinfo, errorp,
		    serverAddrType);
		if ((rc != NS_LDAP_SUCCESS) || (sinfo.server == NULL) ||
		    (host && (strcasecmp(host, sinfo.server) == 0))) {
			/* Log the error */
			if (*errorp) {
				(void) snprintf(errmsg, sizeof (errmsg),
				"%s: (%s)", gettext("makeConnection: "
				"unable to make LDAP connection, "
				"request for a server failed"),
				    (*errorp)->message);
				syslog(LOG_ERR, "libsldap: %s", errmsg);
			}

			__s_api_free_server_info(&sinfo);
			if (host)
				free(host);
			return (NS_LDAP_OP_FAILED);
		}
		if (host)
			free(host);
		host = strdup(sinfo.server);
		if (host == NULL) {
			__s_api_free_server_info(&sinfo);
			return (NS_LDAP_MEMORY);
		}

		/* check if server supports password management */
		passwd_mgmt = __s_api_contain_passwd_control_oid(
		    sinfo.controls);
		/* check if server supports password less account mgmt */
		if (nopasswd_acct_mgmt &&
		    !__s_api_contain_account_usable_control_oid(
		    sinfo.controls)) {
			syslog(LOG_WARNING, "libsldap: server %s does not "
			    "provide account information without password",
			    host);
			free(host);
			__s_api_free_server_info(&sinfo);
			return (NS_LDAP_OP_FAILED);
		}
		/* make the connection */
		rc = openConnection(&ld, *bindHost, auth, timeoutSec, errorp,
		    fail_if_new_pwd_reqd, passwd_mgmt, conn_user);
		/* if success, go to create connection structure */
		if (rc == NS_LDAP_SUCCESS ||
		    rc == NS_LDAP_SUCCESS_WITH_INFO) {
			exit_rc = rc;
			break;
		}

		/*
		 * If not able to reach the server, inform the ldap
		 * cache manager that the server should be removed
		 * from its server list. Thus, the manager will not
		 * return this server on the next get-server request
		 * and will also reduce the server list refresh TTL,
		 * so that it will find out sooner when the server
		 * is up again.
		 */
		if (rc == NS_LDAP_INTERNAL && *errorp != NULL) {
			if ((*errorp)->status == LDAP_CONNECT_ERROR ||
			    (*errorp)->status == LDAP_SERVER_DOWN) {
				/* Reset memory allocation error */
				memerr = 0;
				/*
				 * We contacted a server that we could
				 * not either authenticate to or contact.
				 * If it is due to authentication, then
				 * we need to try the server again. So,
				 * do not remove the server yet, but
				 * add it to the bad server list.
				 * The caller routine will remove
				 * the servers if:
				 *	a). A good server is found or
				 *	b). All the possible methods
				 *	    are tried without finding
				 *	    a good server
				 */
				if (*badsrvrs == NULL) {
					if (!(*badsrvrs = (char **)malloc
					    (sizeof (char *) * NUMTOMALLOC))) {
						memerr = 1;
					}
				/* Allocate memory in chunks of NUMTOMALLOC */
				} else if ((totalbad % NUMTOMALLOC) ==
				    NUMTOMALLOC - 1) {
					char **tmpptr;
					if (!(tmpptr = (char **)realloc(
					    *badsrvrs,
					    (sizeof (char *) * NUMTOMALLOC *
					    ((totalbad/NUMTOMALLOC) + 2))))) {
						memerr = 1;
					} else {
						*badsrvrs = tmpptr;
					}
				}
				/*
				 * Store host only if there were no unsuccessful
				 * memory allocations above
				 */
				if (!memerr &&
				    !((*badsrvrs)[totalbad++] = strdup(host))) {
					memerr = 1;
					totalbad--;
				}
				(*badsrvrs)[totalbad] = NULL;
			}
		}

		/* else, cleanup and go for the next server */
		__s_api_free_server_info(&sinfo);

		/* Return if we had memory allocation errors */
		if (memerr)
			return (NS_LDAP_MEMORY);
		if (*errorp) {
			/*
			 * If openConnection() failed due to
			 * password policy, or invalid credential,
			 * keep *errorp and exit
			 */
			if ((*errorp)->pwd_mgmt.status != NS_PASSWD_GOOD ||
			    (*errorp)->status == LDAP_INVALID_CREDENTIALS) {
				free(host);
				return (rc);
			} else {
				(void) __ns_ldap_freeError(errorp);
				*errorp = NULL;
			}
		}
	}

create_con:
	/* we have created ld, setup con structure */
	if (host)
		free(host);
	if ((con = calloc(1, sizeof (Connection))) == NULL) {
		__s_api_free_server_info(&sinfo);
		/*
		 * If password control attached in **errorp,
		 * e.g. rc == NS_LDAP_SUCCESS_WITH_INFO,
		 * free the error structure
		 */
		if (*errorp) {
			(void) __ns_ldap_freeError(errorp);
			*errorp = NULL;
		}
		(void) ldap_unbind(ld);
		return (NS_LDAP_MEMORY);
	}

	con->serverAddr = sinfo.server; /* Store original format */
	if (sinfo.serverFQDN != NULL) {
		free(sinfo.serverFQDN);
		sinfo.serverFQDN = NULL;
	}
	con->saslMechanisms = sinfo.saslMechanisms;
	con->controls = sinfo.controls;

	con->auth = __ns_ldap_dupAuth(auth);
	if (con->auth == NULL) {
		(void) ldap_unbind(ld);
		__s_api_freeConnection(con);
		/*
		 * If password control attached in **errorp,
		 * e.g. rc == NS_LDAP_SUCCESS_WITH_INFO,
		 * free the error structure
		 */
		if (*errorp) {
			(void) __ns_ldap_freeError(errorp);
			*errorp = NULL;
		}
		return (NS_LDAP_MEMORY);
	}

	con->threadID = thr_self();
	con->pid = getpid();

	con->ld = ld;
	/* add MT connection to the MT connection pool */
	if (conn_user != NULL && conn_user->conn_mt != NULL) {
		if (__s_api_conn_mt_add(con, conn_user, errorp) ==
		    NS_LDAP_SUCCESS) {
			*conp = con;
			return (exit_rc);
		} else {
			(void) ldap_unbind(ld);
			__s_api_freeConnection(con);
			return ((*errorp)->status);
		}
	}

	/* MT connection not supported or not required case */
	if ((id = addConnection(con)) == -1) {
		(void) ldap_unbind(ld);
		__s_api_freeConnection(con);
		/*
		 * If password control attached in **errorp,
		 * e.g. rc == NS_LDAP_SUCCESS_WITH_INFO,
		 * free the error structure
		 */
		if (*errorp) {
			(void) __ns_ldap_freeError(errorp);
			*errorp = NULL;
		}
		return (NS_LDAP_MEMORY);
	}
#ifdef DEBUG
	(void) fprintf(stderr, "tid= %d: connection added into "
	    "cache %d\n", thr_self(), id);
	fflush(stderr);
#endif /* DEBUG */
	*cID = id;
	*conp = con;
	return (exit_rc);
}

/*
 * Return the specified connection to the pool.  If necessary
 * delete the connection.
 */

static void
_DropConnection(ConnectionID cID, int flag, int fini)
{
	Connection *cp;
	int id;
	int use_mutex = !fini;
	struct timeval	zerotime;
	LDAPMessage	*res;

	zerotime.tv_sec = zerotime.tv_usec = 0L;

	id = cID - CONID_OFFSET;
	if (id < 0 || id >= sessionPoolSize)
		return;
#ifdef DEBUG
	(void) fprintf(stderr,
	    "tid %d: Dropping connection cID=%d flag=0x%x\n",
	    thr_self(), cID, flag);
	fflush(stderr);
#endif /* DEBUG */
	if (use_mutex)
		(void) mutex_lock(&sessionPoolLock);

	cp = sessionPool[id];
	/* sanity check before removing */
	if (!cp || (!fini && (!cp->usedBit || cp->threadID != thr_self()))) {
		if (use_mutex)
			(void) mutex_unlock(&sessionPoolLock);
		return;
	}

	if (!fini &&
	    ((flag & NS_LDAP_NEW_CONN) == 0) &&
	    ((flag & NS_LDAP_KEEP_CONN) || __s_api_nscd_proc() ||
	    __s_api_peruser_proc())) {
		/* release Connection (keep alive) */
		cp->usedBit = B_FALSE;
		cp->threadID = 0;	/* unmark the threadID */
		/*
		 * Do sanity cleanup of remaining results.
		 */
		while (ldap_result(cp->ld, LDAP_RES_ANY, LDAP_MSG_ALL,
		    &zerotime, &res) > 0) {
			if (res != NULL)
				(void) ldap_msgfree(res);
		}
		if (use_mutex)
			(void) mutex_unlock(&sessionPoolLock);
	} else {
		/* delete Connection (disconnect) */
		sessionPool[id] = NULL;
		if (use_mutex)
			(void) mutex_unlock(&sessionPoolLock);
		(void) ldap_unbind(cp->ld);
		__s_api_freeConnection(cp);
	}
}

void
DropConnection(ConnectionID cID, int flag)
{
	_DropConnection(cID, flag, 0);
}

/*
 * This routine is called after a bind operation is
 * done in openConnection() to process the password
 * management information, if any.
 *
 * Input:
 *   bind_type: "simple" or "sasl/DIGEST-MD5"
 *   ldaprc   : ldap rc from the ldap bind operation
 *   controls : controls returned by the server
 *   errmsg   : error message from the server
 *   fail_if_new_pwd_reqd:
 *              flag indicating if connection should be open
 *              when password needs to change immediately
 *   passwd_mgmt:
 *              flag indicating if server supports password
 *              policy/management
 *
 * Output     : ns_ldap_error structure, which may contain
 *              password status and number of seconds until
 *              expired
 *
 * return rc:
 * NS_LDAP_EXTERNAL: error, connection should not open
 * NS_LDAP_SUCCESS_WITH_INFO: OK to open but password info attached
 * NS_LDAP_SUCCESS: OK to open connection
 *
 */

static int
process_pwd_mgmt(char *bind_type, int ldaprc,
		LDAPControl **controls,
		char *errmsg, ns_ldap_error_t **errorp,
		int fail_if_new_pwd_reqd,
		int passwd_mgmt)
{
	char		errstr[MAXERROR];
	LDAPControl	**ctrl = NULL;
	int		exit_rc;
	ns_ldap_passwd_status_t	pwd_status = NS_PASSWD_GOOD;
	int		sec_until_exp = 0;

	/*
	 * errmsg may be an empty string,
	 * even if ldaprc is LDAP_SUCCESS,
	 * free the empty string if that's the case
	 */
	if (errmsg &&
	    (*errmsg == '\0' || ldaprc == LDAP_SUCCESS)) {
		ldap_memfree(errmsg);
		errmsg = NULL;
	}

	if (ldaprc != LDAP_SUCCESS) {
		/*
		 * try to map ldap rc and error message to
		 * a password status
		 */
		if (errmsg) {
			if (passwd_mgmt)
				pwd_status =
				    __s_api_set_passwd_status(
				    ldaprc, errmsg);
			ldap_memfree(errmsg);
		}

		(void) snprintf(errstr, sizeof (errstr),
		    gettext("openConnection: "
		    "%s bind failed "
		    "- %s"), bind_type, ldap_err2string(ldaprc));

		if (pwd_status != NS_PASSWD_GOOD) {
			MKERROR_PWD_MGMT(*errorp,
			    ldaprc, strdup(errstr),
			    pwd_status, 0, NULL);
		} else {
			MKERROR(LOG_ERR, *errorp, ldaprc, strdup(errstr),
			    NS_LDAP_MEMORY);
		}
		if (controls)
			ldap_controls_free(controls);

		return (NS_LDAP_INTERNAL);
	}

	/*
	 * ldaprc is LDAP_SUCCESS,
	 * process the password management controls, if any
	 */
	exit_rc = NS_LDAP_SUCCESS;
	if (controls && passwd_mgmt) {
		/*
		 * The control with the OID
		 * 2.16.840.1.113730.3.4.4 (or
		 * LDAP_CONTROL_PWEXPIRED, as defined
		 * in the ldap.h header file) is the
		 * expired password control.
		 *
		 * This control is used if the server
		 * is configured to require users to
		 * change their passwords when first
		 * logging in and whenever the
		 * passwords are reset.
		 *
		 * If the user is logging in for the
		 * first time or if the user's
		 * password has been reset, the
		 * server sends this control to
		 * indicate that the client needs to
		 * change the password immediately.
		 *
		 * At this point, the only operation
		 * that the client can perform is to
		 * change the user's password. If the
		 * client requests any other LDAP
		 * operation, the server sends back
		 * an LDAP_UNWILLING_TO_PERFORM
		 * result code with an expired
		 * password control.
		 *
		 * The control with the OID
		 * 2.16.840.1.113730.3.4.5 (or
		 * LDAP_CONTROL_PWEXPIRING, as
		 * defined in the ldap.h header file)
		 * is the password expiration warning
		 * control.
		 *
		 * This control is used if the server
		 * is configured to expire user
		 * passwords after a certain amount
		 * of time.
		 *
		 * The server sends this control back
		 * to the client if the client binds
		 * using a password that will soon
		 * expire.  The ldctl_value field of
		 * the LDAPControl structure
		 * specifies the number of seconds
		 * before the password will expire.
		 */
		for (ctrl = controls; *ctrl; ctrl++) {

			if (strcmp((*ctrl)->ldctl_oid,
			    LDAP_CONTROL_PWEXPIRED) == 0) {
				/*
				 * if the caller wants this bind
				 * to fail, set up the error info.
				 * If call to this function is
				 * for searching the LDAP directory,
				 * e.g., __ns_ldap_list(),
				 * there's really no sense to
				 * let a connection open and
				 * then fail immediately afterward
				 * on the LDAP search operation with
				 * the LDAP_UNWILLING_TO_PERFORM rc
				 */
				pwd_status =
				    NS_PASSWD_CHANGE_NEEDED;
				if (fail_if_new_pwd_reqd) {
					(void) snprintf(errstr,
					    sizeof (errstr),
					    gettext(
					    "openConnection: "
					    "%s bind "
					    "failed "
					    "- password "
					    "expired. It "
					    " needs to change "
					    "immediately!"),
					    bind_type);
					MKERROR_PWD_MGMT(*errorp,
					    LDAP_SUCCESS,
					    strdup(errstr),
					    pwd_status,
					    0,
					    NULL);
					exit_rc = NS_LDAP_INTERNAL;
				} else {
					MKERROR_PWD_MGMT(*errorp,
					    LDAP_SUCCESS,
					    NULL,
					    pwd_status,
					    0,
					    NULL);
					exit_rc =
					    NS_LDAP_SUCCESS_WITH_INFO;
				}
				break;
			} else if (strcmp((*ctrl)->ldctl_oid,
			    LDAP_CONTROL_PWEXPIRING) == 0) {
				pwd_status =
				    NS_PASSWD_ABOUT_TO_EXPIRE;
				if ((*ctrl)->
				    ldctl_value.bv_len > 0 &&
				    (*ctrl)->
				    ldctl_value.bv_val)
					sec_until_exp =
					    atoi((*ctrl)->
					    ldctl_value.bv_val);
				MKERROR_PWD_MGMT(*errorp,
				    LDAP_SUCCESS,
				    NULL,
				    pwd_status,
				    sec_until_exp,
				    NULL);
				exit_rc =
				    NS_LDAP_SUCCESS_WITH_INFO;
				break;
			}
		}
	}

	if (controls)
		ldap_controls_free(controls);

	return (exit_rc);
}

static int
ldap_in_nss_switch(char *db)
{
	enum __nsw_parse_err		pserr;
	struct __nsw_switchconfig	*conf;
	struct __nsw_lookup		*lkp;
	const char			*name;
	int				found = 0;

	conf = __nsw_getconfig(db, &pserr);
	if (conf == NULL) {
		return (-1);
	}

	/* check for skip and count other backends */
	for (lkp = conf->lookups; lkp != NULL; lkp = lkp->next) {
		name = lkp->service_name;
		if (strcmp(name, "ldap") == 0) {
			found = 1;
			break;
		}
	}
	(void) __nsw_freeconfig(conf);
	return (found);
}

static int
openConnection(LDAP **ldp, const char *serverAddr, const ns_cred_t *auth,
	int timeoutSec, ns_ldap_error_t **errorp,
	int fail_if_new_pwd_reqd, int passwd_mgmt,
	ns_conn_user_t *conn_user)
{
	LDAP			*ld = NULL;
	int			ldapVersion = LDAP_VERSION3;
	int			derefOption = LDAP_DEREF_ALWAYS;
	int			zero = 0;
	int			timeoutMilliSec = timeoutSec * 1000;
	uint16_t		port = USE_DEFAULT_PORT;
	char			*s;
	char			errstr[MAXERROR];

	ns_ldap_return_code	ret_code = NS_LDAP_SUCCESS;

	*errorp = NULL;
	*ldp = NULL;

	/* determine if the host name contains a port number */
	s = strchr(serverAddr, ']');	/* skip over ipv6 addr */
	s = strchr(s != NULL ? s : serverAddr, ':');
	if (s != NULL) {
		if (sscanf(s + 1, "%hu", &port) != 1) {
			(void) snprintf(errstr,
			    sizeof (errstr),
			    gettext("openConnection: cannot "
			    "convert %s into a valid "
			    "port number for the "
			    "%s server. A default value "
			    "will be used."),
			    s,
			    serverAddr);
			syslog(LOG_ERR, "libsldap: %s", errstr);
		} else {
			*s = '\0';
		}
	}

	ret_code = createSession(auth,
	    serverAddr,
	    port,
	    timeoutMilliSec,
	    &ld,
	    errorp);
	if (s != NULL) {
		*s = ':';
	}
	if (ret_code != NS_LDAP_SUCCESS) {
		return (ret_code);
	}

	/* check to see if the underlying libsldap supports MT connection */
	if (conn_user != NULL) {
		int rc;

		rc = __s_api_check_libldap_MT_conn_support(conn_user, ld,
		    errorp);
		if (rc != NS_LDAP_SUCCESS) {
			(void) ldap_unbind(ld);
			return (rc);
		}
	}

	(void) ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion);
	(void) ldap_set_option(ld, LDAP_OPT_DEREF, &derefOption);
	/*
	 * set LDAP_OPT_REFERRALS to OFF.
	 * This library will handle the referral itself
	 * based on API flags or configuration file
	 * specification. If this option is not set
	 * to OFF, libldap will never pass the
	 * referral info up to this library
	 */
	(void) ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF);
	(void) ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &zero);
	(void) ldap_set_option(ld, LDAP_OPT_SIZELIMIT, &zero);
	/* setup TCP/IP connect timeout */
	(void) ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT,
	    &timeoutMilliSec);
	/* retry if LDAP I/O was interrupted */
	(void) ldap_set_option(ld, LDAP_OPT_RESTART, LDAP_OPT_ON);

	ret_code = performBind(auth,
	    ld,
	    timeoutSec,
	    errorp,
	    fail_if_new_pwd_reqd,
	    passwd_mgmt);

	if (ret_code == NS_LDAP_SUCCESS ||
	    ret_code == NS_LDAP_SUCCESS_WITH_INFO) {
		*ldp = ld;
	}

	return (ret_code);
}

/*
 * FUNCTION:	__s_api_getDefaultAuth
 *
 *	Constructs a credential for authentication using the config module.
 *
 * RETURN VALUES:
 *
 * NS_LDAP_SUCCESS	If successful
 * NS_LDAP_CONFIG	If there are any config errors.
 * NS_LDAP_MEMORY	Memory errors.
 * NS_LDAP_OP_FAILED	If there are no more authentication methods so can
 *			not build a new authp.
 * NS_LDAP_INVALID_PARAM This overloaded return value means that some of the
 *			necessary fields of a cred for a given auth method
 *			are not provided.
 * INPUT:
 *
 * cLevel	Currently requested credential level to be tried
 *
 * aMethod	Currently requested authentication method to be tried
 *
 * getAdmin	If non 0,  get Admin -i.e., not proxyAgent- DN and password
 *
 * OUTPUT:
 *
 * authp		authentication method to use.
 */
static int
__s_api_getDefaultAuth(
	int	*cLevel,
	ns_auth_t *aMethod,
	ns_cred_t **authp,
	int	getAdmin)
{
	void		**paramVal = NULL;
	char		*modparamVal = NULL;
	int		getUid = 0;
	int		getPasswd = 0;
	int		getCertpath = 0;
	int		rc = 0;
	ns_ldap_error_t	*errorp = NULL;
	UnixCred_t	*AdminCred = NULL;

#ifdef DEBUG
	(void) fprintf(stderr, "__s_api_getDefaultAuth START\n");
#endif

	if (aMethod == NULL) {
		/* Require an Auth */
		return (NS_LDAP_INVALID_PARAM);

	}
	/*
	 * credential level "self" can work with auth method sasl/GSSAPI only
	 */
	if (cLevel && *cLevel == NS_LDAP_CRED_SELF &&
	    aMethod->saslmech != NS_LDAP_SASL_GSSAPI)
		return (NS_LDAP_INVALID_PARAM);

	*authp = (ns_cred_t *)calloc(1, sizeof (ns_cred_t));
	if ((*authp) == NULL)
		return (NS_LDAP_MEMORY);

	(*authp)->auth = *aMethod;

	switch (aMethod->type) {
		case NS_LDAP_AUTH_NONE:
			return (NS_LDAP_SUCCESS);
		case NS_LDAP_AUTH_SIMPLE:
			getUid++;
			getPasswd++;
			break;
		case NS_LDAP_AUTH_SASL:
			if ((aMethod->saslmech == NS_LDAP_SASL_DIGEST_MD5) ||
			    (aMethod->saslmech == NS_LDAP_SASL_CRAM_MD5)) {
				getUid++;
				getPasswd++;
			} else if (aMethod->saslmech != NS_LDAP_SASL_GSSAPI) {
				(void) __ns_ldap_freeCred(authp);
				return (NS_LDAP_INVALID_PARAM);
			}
			break;
		case NS_LDAP_AUTH_TLS:
			if ((aMethod->tlstype == NS_LDAP_TLS_SIMPLE) ||
			    ((aMethod->tlstype == NS_LDAP_TLS_SASL) &&
			    ((aMethod->saslmech == NS_LDAP_SASL_DIGEST_MD5) ||
			    (aMethod->saslmech == NS_LDAP_SASL_CRAM_MD5)))) {
				getUid++;
				getPasswd++;
				getCertpath++;
			} else if (aMethod->tlstype == NS_LDAP_TLS_NONE) {
				getCertpath++;
			} else {
				(void) __ns_ldap_freeCred(authp);
				return (NS_LDAP_INVALID_PARAM);
			}
			break;
	}

	if (getUid) {
		paramVal = NULL;
		if (getAdmin) {
			/*
			 * Assume AdminCred has been retrieved from
			 * ldap_cachemgr already. It will not work
			 * without userID or password. Flags getUid
			 * and getPasswd should always be set
			 * together.
			 */
			AdminCred = calloc(1, sizeof (UnixCred_t));
			if (AdminCred == NULL) {
				(void) __ns_ldap_freeCred(authp);
				return (NS_LDAP_MEMORY);
			}

			rc = requestAdminCred(&AdminCred, &errorp);
			if (rc != NS_LDAP_SUCCESS) {
				(void) __ns_ldap_freeCred(authp);
				(void) __ns_ldap_freeUnixCred(&AdminCred);
				(void) __ns_ldap_freeError(&errorp);
				return (rc);
			}

			if (AdminCred->userID == NULL) {
				(void) __ns_ldap_freeCred(authp);
				(void) __ns_ldap_freeUnixCred(&AdminCred);
				return (NS_LDAP_INVALID_PARAM);
			}
			(*authp)->cred.unix_cred.userID = AdminCred->userID;
			AdminCred->userID = NULL;
		} else {
			rc = __ns_ldap_getParam(NS_LDAP_BINDDN_P,
			    &paramVal, &errorp);
			if (rc != NS_LDAP_SUCCESS) {
				(void) __ns_ldap_freeCred(authp);
				(void) __ns_ldap_freeError(&errorp);
				return (rc);
			}

			if (paramVal == NULL || *paramVal == NULL) {
				(void) __ns_ldap_freeCred(authp);
				return (NS_LDAP_INVALID_PARAM);
			}

			(*authp)->cred.unix_cred.userID =
			    strdup((char *)*paramVal);
			(void) __ns_ldap_freeParam(&paramVal);
		}
		if ((*authp)->cred.unix_cred.userID == NULL) {
			(void) __ns_ldap_freeCred(authp);
			(void) __ns_ldap_freeUnixCred(&AdminCred);
			return (NS_LDAP_MEMORY);
		}
	}
	if (getPasswd) {
		paramVal = NULL;
		if (getAdmin) {
			/*
			 * Assume AdminCred has been retrieved from
			 * ldap_cachemgr already. It will not work
			 * without the userID anyway because for
			 * getting admin credential, flags getUid
			 * and getPasswd should always be set
			 * together.
			 */
			if (AdminCred == NULL || AdminCred->passwd == NULL) {
				(void) __ns_ldap_freeCred(authp);
				(void) __ns_ldap_freeUnixCred(&AdminCred);
				return (NS_LDAP_INVALID_PARAM);
			}
			modparamVal = dvalue(AdminCred->passwd);
		} else {
			rc = __ns_ldap_getParam(NS_LDAP_BINDPASSWD_P,
			    &paramVal, &errorp);
			if (rc != NS_LDAP_SUCCESS) {
				(void) __ns_ldap_freeCred(authp);
				(void) __ns_ldap_freeError(&errorp);
				return (rc);
			}

			if (paramVal == NULL || *paramVal == NULL) {
				(void) __ns_ldap_freeCred(authp);
				return (NS_LDAP_INVALID_PARAM);
			}

			modparamVal = dvalue((char *)*paramVal);
			(void) __ns_ldap_freeParam(&paramVal);
		}

		if (modparamVal == NULL || (strlen((char *)modparamVal) == 0)) {
			(void) __ns_ldap_freeCred(authp);
			(void) __ns_ldap_freeUnixCred(&AdminCred);
			if (modparamVal != NULL)
				free(modparamVal);
			return (NS_LDAP_INVALID_PARAM);
		}

		(*authp)->cred.unix_cred.passwd = modparamVal;
	}
	if (getCertpath) {
		paramVal = NULL;
		if ((rc = __ns_ldap_getParam(NS_LDAP_HOST_CERTPATH_P,
		    &paramVal, &errorp)) != NS_LDAP_SUCCESS) {
			(void) __ns_ldap_freeCred(authp);
			(void) __ns_ldap_freeUnixCred(&AdminCred);
			(void) __ns_ldap_freeError(&errorp);
			*authp = NULL;
			return (rc);
		}

		if (paramVal == NULL || *paramVal == NULL) {
			(void) __ns_ldap_freeCred(authp);
			(void) __ns_ldap_freeUnixCred(&AdminCred);
			*authp = NULL;
			return (NS_LDAP_INVALID_PARAM);
		}

		(*authp)->hostcertpath = strdup((char *)*paramVal);
		(void) __ns_ldap_freeParam(&paramVal);
		if ((*authp)->hostcertpath == NULL) {
			(void) __ns_ldap_freeCred(authp);
			(void) __ns_ldap_freeUnixCred(&AdminCred);
			*authp = NULL;
			return (NS_LDAP_MEMORY);
		}
	}
	(void) __ns_ldap_freeUnixCred(&AdminCred);
	return (NS_LDAP_SUCCESS);
}

/*
 * FUNCTION:	getConnection
 *
 *	internal version of __s_api_getConnection()
 */
static int
getConnection(
	const char *server,
	const int flags,
	const ns_cred_t *cred,		/* credentials for bind */
	ConnectionID *sessionId,
	Connection **session,
	ns_ldap_error_t **errorp,
	int fail_if_new_pwd_reqd,
	int nopasswd_acct_mgmt,
	ns_conn_user_t *conn_user)
{
	char		errmsg[MAXERROR];
	ns_auth_t	**aMethod = NULL;
	ns_auth_t	**aNext = NULL;
	int		**cLevel = NULL;
	int		**cNext = NULL;
	int		timeoutSec = NS_DEFAULT_BIND_TIMEOUT;
	int		rc;
	Connection	*con = NULL;
	int		sec = 1;
	ns_cred_t 	*authp = NULL;
	ns_cred_t	anon;
	int		version = NS_LDAP_V2, self_gssapi_only = 0;
	void		**paramVal = NULL;
	char		**badSrvrs = NULL; /* List of problem hostnames */

	if ((session == NULL) || (sessionId == NULL)) {
		return (NS_LDAP_INVALID_PARAM);
	}
	*session = NULL;

	/* reuse MT connection if needed and if available */
	if (conn_user != NULL) {
		rc = __s_api_conn_mt_get(server, flags, cred, session, errorp,
		    conn_user);
		if (rc != NS_LDAP_NOTFOUND)
			return (rc);
	}

	/* get profile version number */
	if ((rc = __ns_ldap_getParam(NS_LDAP_FILE_VERSION_P,
	    &paramVal, errorp)) != NS_LDAP_SUCCESS)
		return (rc);
	if (paramVal == NULL) {
		(void) sprintf(errmsg, gettext("getConnection: no file "
		    "version"));
		MKERROR(LOG_WARNING, *errorp, NS_CONFIG_FILE, strdup(errmsg),
		    NS_LDAP_CONFIG);
		return (NS_LDAP_CONFIG);
	}
	if (strcasecmp((char *)*paramVal, NS_LDAP_VERSION_1) == 0)
		version = NS_LDAP_V1;
	(void) __ns_ldap_freeParam((void ***)&paramVal);

	/* Get the bind timeout value */
	(void) __ns_ldap_getParam(NS_LDAP_BIND_TIME_P, &paramVal, errorp);
	if (paramVal != NULL && *paramVal != NULL) {
		timeoutSec = **((int **)paramVal);
		(void) __ns_ldap_freeParam(&paramVal);
	}
	if (*errorp)
		(void) __ns_ldap_freeError(errorp);

	if (cred == NULL) {
		/* Get the authentication method list */
		if ((rc = __ns_ldap_getParam(NS_LDAP_AUTH_P,
		    (void ***)&aMethod, errorp)) != NS_LDAP_SUCCESS)
			return (rc);
		if (aMethod == NULL) {
			aMethod = (ns_auth_t **)calloc(2, sizeof (ns_auth_t *));
			if (aMethod == NULL)
				return (NS_LDAP_MEMORY);
			aMethod[0] = (ns_auth_t *)calloc(1, sizeof (ns_auth_t));
			if (aMethod[0] == NULL) {
				free(aMethod);
				return (NS_LDAP_MEMORY);
			}
			if (version == NS_LDAP_V1)
				(aMethod[0])->type = NS_LDAP_AUTH_SIMPLE;
			else {
				(aMethod[0])->type = NS_LDAP_AUTH_SASL;
				(aMethod[0])->saslmech =
				    NS_LDAP_SASL_DIGEST_MD5;
				(aMethod[0])->saslopt = NS_LDAP_SASLOPT_NONE;
			}
		}

		/* Get the credential level list */
		if ((rc = __ns_ldap_getParam(NS_LDAP_CREDENTIAL_LEVEL_P,
		    (void ***)&cLevel, errorp)) != NS_LDAP_SUCCESS) {
			(void) __ns_ldap_freeParam((void ***)&aMethod);
			return (rc);
		}
		if (cLevel == NULL) {
			cLevel = (int **)calloc(2, sizeof (int *));
			if (cLevel == NULL)
				return (NS_LDAP_MEMORY);
			cLevel[0] = (int *)calloc(1, sizeof (int));
			if (cLevel[0] == NULL)
				return (NS_LDAP_MEMORY);
			if (version == NS_LDAP_V1)
				*(cLevel[0]) = NS_LDAP_CRED_PROXY;
			else
				*(cLevel[0]) = NS_LDAP_CRED_ANON;
		}
	}

	/* setup the anon credential for anonymous connection */
	(void) memset(&anon, 0, sizeof (ns_cred_t));
	anon.auth.type = NS_LDAP_AUTH_NONE;

	for (;;) {
		if (cred != NULL) {
			/* using specified auth method */
			rc = makeConnection(&con, server, cred,
			    sessionId, timeoutSec, errorp,
			    fail_if_new_pwd_reqd,
			    nopasswd_acct_mgmt, flags, &badSrvrs, conn_user);
			/* not using bad server if credentials were supplied */
			if (badSrvrs && *badSrvrs) {
				__s_api_free2dArray(badSrvrs);
				badSrvrs = NULL;
			}
			if (rc == NS_LDAP_SUCCESS ||
			    rc == NS_LDAP_SUCCESS_WITH_INFO) {
				*session = con;
				break;
			}
		} else {
			self_gssapi_only = __s_api_self_gssapi_only_get();
			/* for every cred level */
			for (cNext = cLevel; *cNext != NULL; cNext++) {
				if (self_gssapi_only &&
				    **cNext != NS_LDAP_CRED_SELF)
					continue;
				if (**cNext == NS_LDAP_CRED_ANON) {
					/*
					 * make connection anonymously
					 * Free the down server list before
					 * looping through
					 */
					if (badSrvrs && *badSrvrs) {
						__s_api_free2dArray(badSrvrs);
						badSrvrs = NULL;
					}
					rc = makeConnection(&con, server, &anon,
					    sessionId, timeoutSec, errorp,
					    fail_if_new_pwd_reqd,
					    nopasswd_acct_mgmt, flags,
					    &badSrvrs, conn_user);
					if (rc == NS_LDAP_SUCCESS ||
					    rc ==
					    NS_LDAP_SUCCESS_WITH_INFO) {
						*session = con;
						goto done;
					}
					continue;
				}
				/* for each cred level */
				for (aNext = aMethod; *aNext != NULL; aNext++) {
					if (self_gssapi_only &&
					    (*aNext)->saslmech !=
					    NS_LDAP_SASL_GSSAPI)
						continue;
					/*
					 * self coexists with sasl/GSSAPI only
					 * and non-self coexists with non-gssapi
					 * only
					 */
					if ((**cNext == NS_LDAP_CRED_SELF &&
					    (*aNext)->saslmech !=
					    NS_LDAP_SASL_GSSAPI) ||
					    (**cNext != NS_LDAP_CRED_SELF &&
					    (*aNext)->saslmech ==
					    NS_LDAP_SASL_GSSAPI))
						continue;
					/* make connection and authenticate */
					/* with default credentials */
					authp = NULL;
					rc = __s_api_getDefaultAuth(*cNext,
					    *aNext, &authp,
					    flags & NS_LDAP_READ_SHADOW);
					if (rc != NS_LDAP_SUCCESS) {
						continue;
					}
					/*
					 * Free the down server list before
					 * looping through
					 */
					if (badSrvrs && *badSrvrs) {
						__s_api_free2dArray(badSrvrs);
						badSrvrs = NULL;
					}
					rc = makeConnection(&con, server, authp,
					    sessionId, timeoutSec, errorp,
					    fail_if_new_pwd_reqd,
					    nopasswd_acct_mgmt, flags,
					    &badSrvrs, conn_user);
					(void) __ns_ldap_freeCred(&authp);
					if (rc == NS_LDAP_SUCCESS ||
					    rc ==
					    NS_LDAP_SUCCESS_WITH_INFO) {
						*session = con;
						goto done;
					}
				}
			}
		}
		if (flags & NS_LDAP_HARD) {
			if (sec < LDAPMAXHARDLOOKUPTIME)
				sec *= 2;
			(void) sleep(sec);
		} else {
			break;
		}
	}

done:
	if (self_gssapi_only && rc == NS_LDAP_SUCCESS && *session == NULL) {
		/*
		 * self_gssapi_only is true but no self/sasl/gssapi is
		 * configured
		 */
		rc = NS_LDAP_CONFIG;
	}

	(void) __ns_ldap_freeParam((void ***)&aMethod);
	(void) __ns_ldap_freeParam((void ***)&cLevel);

	if (badSrvrs && *badSrvrs) {
		/*
		 * At this point, either we have a successful
		 * connection or exhausted all the possible auths.
		 * and creds. Mark the problem servers as down
		 * so that the problem servers are not contacted
		 * again until the refresh_ttl expires.
		 */
		(void) __s_api_removeBadServers(badSrvrs);
		__s_api_free2dArray(badSrvrs);
	}
	return (rc);
}

/*
 * FUNCTION:	__s_api_getConnection
 *
 *	Bind to the specified server or one from the server
 *	list and return the pointer.
 *
 *	This function can rebind or not (NS_LDAP_HARD), it can require a
 *	credential or bind anonymously
 *
 *	This function follows the DUA configuration schema algorithm
 *
 * RETURN VALUES:
 *
 * NS_LDAP_SUCCESS	A connection was made successfully.
 * NS_LDAP_SUCCESS_WITH_INFO
 * 			A connection was made successfully, but with
 *			password management info in *errorp
 * NS_LDAP_INVALID_PARAM If any invalid arguments were passed to the function.
 * NS_LDAP_CONFIG	If there are any config errors.
 * NS_LDAP_MEMORY	Memory errors.
 * NS_LDAP_INTERNAL	If there was a ldap error.
 *
 * INPUT:
 *
 * server	Bind to this LDAP server only
 * flags	If NS_LDAP_HARD is set function will not return until it has
 *		a connection unless there is a authentication problem.
 *		If NS_LDAP_NEW_CONN is set the function must force a new
 *              connection to be created
 *		If NS_LDAP_KEEP_CONN is set the connection is to be kept open
 * auth		Credentials for bind. This could be NULL in which case
 *		a default cred built from the config module is used.
 * sessionId	cookie that points to a previous session
 * fail_if_new_pwd_reqd
 *		a flag indicating this function should fail if the passwd
 *		in auth needs to change immediately
 * nopasswd_acct_mgmt
 *		a flag indicating that makeConnection should check before
 *		binding if server supports LDAP V3 password less
 *		account management
 *
 * OUTPUT:
 *
 * session	pointer to a session with connection information
 * errorp	Set if there are any INTERNAL, or CONFIG error.
 */
int
__s_api_getConnection(
	const char *server,
	const int flags,
	const ns_cred_t *cred,		/* credentials for bind */
	ConnectionID *sessionId,
	Connection **session,
	ns_ldap_error_t **errorp,
	int fail_if_new_pwd_reqd,
	int nopasswd_acct_mgmt,
	ns_conn_user_t *conn_user)
{
	int rc;

	rc = getConnection(server, flags, cred, sessionId, session,
	    errorp, fail_if_new_pwd_reqd, nopasswd_acct_mgmt,
	    conn_user);

	if (rc != NS_LDAP_SUCCESS && rc != NS_LDAP_SUCCESS_WITH_INFO) {
		if (conn_user != NULL && conn_user->conn_mt != NULL)
			__s_api_conn_mt_remove(conn_user, rc, errorp);
	}

	return (rc);
}

void
__s_api_free_sessionPool()
{
	int id;

	(void) mutex_lock(&sessionPoolLock);

	if (sessionPool != NULL) {
		for (id = 0; id < sessionPoolSize; id++)
			_DropConnection(id + CONID_OFFSET, 0, 1);
		free(sessionPool);
		sessionPool = NULL;
		sessionPoolSize = 0;
	}
	(void) mutex_unlock(&sessionPoolLock);
}

/*
 * This function initializes a TLS LDAP session. On success LDAP* is returned
 * (pointed by *ldp). Otherwise, the function returns an NS error code and
 * provide an additional info pointed by *errorp.
 */
static
ns_ldap_return_code
createTLSSession(const ns_cred_t *auth, const char *serverAddr,
		    uint16_t port, int timeoutMilliSec,
		    LDAP **ldp, ns_ldap_error_t **errorp)
{
	const char	*hostcertpath;
	char		*alloc_hcp = NULL, errstr[MAXERROR];
	int		ldap_rc;

#ifdef DEBUG
	(void) fprintf(stderr, "tid= %d: +++TLS transport\n",
	    thr_self());
#endif /* DEBUG */

	if (prldap_set_session_option(NULL, NULL,
	    PRLDAP_OPT_IO_MAX_TIMEOUT,
	    timeoutMilliSec) != LDAP_SUCCESS) {
		(void) snprintf(errstr, sizeof (errstr),
		    gettext("createTLSSession: failed to initialize "
		    "TLS security"));
		MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR,
		    strdup(errstr), NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	hostcertpath = auth->hostcertpath;
	if (hostcertpath == NULL) {
		alloc_hcp = __s_get_hostcertpath();
		hostcertpath = alloc_hcp;
	}

	if (hostcertpath == NULL)
		return (NS_LDAP_MEMORY);

	if ((ldap_rc = ldapssl_client_init(hostcertpath, NULL)) < 0) {
		if (alloc_hcp != NULL) {
			free(alloc_hcp);
		}
		(void) snprintf(errstr, sizeof (errstr),
		    gettext("createTLSSession: failed to initialize "
		    "TLS security (%s)"),
		    ldapssl_err2string(ldap_rc));
		MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR,
		    strdup(errstr), NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}
	if (alloc_hcp)
		free(alloc_hcp);

	*ldp = ldapssl_init(serverAddr, port, 1);

	if (*ldp == NULL ||
	    ldapssl_install_gethostbyaddr(*ldp, "ldap") != 0) {
		(void) snprintf(errstr, sizeof (errstr),
		    gettext("createTLSSession: failed to connect "
		    "using TLS (%s)"), strerror(errno));
		MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR,
		    strdup(errstr), NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	return (NS_LDAP_SUCCESS);
}

/*
 * Convert (resolve) hostname to IP address.
 *
 * INPUT:
 *
 * 	server	- \[IPv6_address\][:port]
 *		- IPv4_address[:port]
 *		- hostname[:port]
 *
 * 	newaddr - Buffer to which this function writes resulting address,
 *		including the port number, if specified in server argument.
 *
 * 	newaddr_size - Size of the newaddr buffer.
 *
 * 	errstr  - Buffer to which error string is written if error occurs.
 *
 * 	errstr_size - Size of the errstr buffer.
 *
 * OUTPUT:
 *
 * 	Returns 1 for success, 0 in case of error.
 *
 * 	newaddr - See above (INPUT section).
 *
 *	errstr	- See above (INPUT section).
 */
static int
cvt_hostname2ip(char *server, char *newaddr, int newaddr_size,
    char *errstr, int errstr_size)
{
	char	*s;
	unsigned short port = 0;
	int	err;
	char	buffer[NSS_BUFLEN_HOSTS];
	struct hostent	result;

	/* Determine if the host name contains a port number. */

	/* Skip over IPv6 address. */
	s = strchr(server, ']');
	s = strchr(s != NULL ? s : server, ':');
	if (s != NULL) {
		if (sscanf(s + 1, "%hu", &port) != 1) {
			/* Address misformatted. No port number after : */
			(void) snprintf(errstr, errstr_size, "%s",
			    gettext("Invalid host:port format"));
			return (0);
		} else
			/* Cut off the :<port> part. */
			*s = '\0';
	}

	buffer[0] = '\0';
	/*
	 * Resolve hostname and fill in hostent structure.
	 */
	if (!__s_api_hostname2ip(server, &result, buffer, NSS_BUFLEN_HOSTS,
	    &err)) {
		/*
		 * The only possible error here could be TRY_AGAIN if buffer was
		 * not big enough. NSS_BUFLEN_HOSTS should have been enough
		 * though.
		 */
		(void) snprintf(errstr, errstr_size, "%s",
		    gettext("Unable to resolve address."));
		return (0);
	}


	buffer[0] = '\0';
	/*
	 * Convert the address to string.
	 */
	if (!inet_ntop(result.h_addrtype, result.h_addr_list[0], buffer,
	    NSS_BUFLEN_HOSTS)) {
		/* There's not much we can do. */
		(void) snprintf(errstr, errstr_size, "%s",
		    gettext("Unable to convert address to string."));
		return (0);
	}

	/* Put together the address and the port */
	if (port > 0) {
		switch (result.h_addrtype) {
			case AF_INET6:
				(void) snprintf(newaddr,
				    /* [IP]:<port>\0 */
				    1 + strlen(buffer) + 1 + 1 + 5 + 1,
				    "[%s]:%hu",
				    buffer,
				    port);
				break;
			/* AF_INET */
			default :
				(void) snprintf(newaddr,
				    /* IP:<port>\0 */
				    strlen(buffer) + 1 + 5 + 1,
				    "%s:%hu",
				    buffer,
				    port);
				break;
		}
	} else {
		(void) strncpy(newaddr, buffer, newaddr_size);
	}

	return (1);
}


/*
 * This finction initializes a none-TLS LDAP session.  On success LDAP*
 * is returned (pointed by *ldp). Otherwise, the function returns
 * an NS error code and provides an additional info pointed by *errorp.
 */
static
ns_ldap_return_code
createNonTLSSession(const char *serverAddr,
		uint16_t port, int gssapi,
		LDAP **ldp, ns_ldap_error_t **errorp)
{
	char		errstr[MAXERROR];
	char		*addr;
	int		is_ip = 0;
			/* [INET6_ADDRSTRLEN]:<port>\0 */
	char		svraddr[1+INET6_ADDRSTRLEN+1+1+5+1];
#ifdef DEBUG
	(void) fprintf(stderr, "tid= %d: +++Unsecure transport\n",
	    thr_self());
#endif /* DEBUG */

	if (gssapi == 0) {
		is_ip = (__s_api_isipv4((char *)serverAddr) ||
		    __s_api_isipv6((char *)serverAddr));
	}

	/*
	 * Let's try to resolve IP address of server.
	 */
	if (is_ip == 0 && !gssapi && (ldap_in_nss_switch((char *)"hosts") > 0 ||
	    ldap_in_nss_switch((char *)"ipnodes") > 0)) {
		addr = strdup(serverAddr);
		if (addr == NULL)
			return (NS_LDAP_MEMORY);
		svraddr[0] = '\0';
		if (cvt_hostname2ip(addr, svraddr, sizeof (svraddr),
		    errstr, MAXERROR) == 1) {
			serverAddr = svraddr;
			free(addr);
		} else {
			free(addr);
			MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR,
			    strdup(errstr), NS_LDAP_MEMORY);
			return (NS_LDAP_INTERNAL);
		}
	}

	/* Warning message IF cannot connect to host(s) */
	if ((*ldp = ldap_init((char *)serverAddr, port)) == NULL) {
		char *p = strerror(errno);
		MKERROR(LOG_WARNING, *errorp, LDAP_CONNECT_ERROR,
		    strdup(p), NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	return (NS_LDAP_SUCCESS);
}

/*
 * This finction initializes an LDAP session.
 *
 * INPUT:
 *     auth - a structure specified an authenticastion method and credentials,
 *     serverAddr - the address of a server to which a connection
 *                  will be established,
 *     port - a port being listened by the server,
 *     timeoutMilliSec - a timeout in milliseconds for the Bind operation.
 *
 * OUTPUT:
 *     ldp - a pointer to an LDAP structure which will be used
 *           for all the subsequent operations against the server.
 *     If an error occurs, the function returns an NS error code
 *     and provides an additional info pointed by *errorp.
 */
static
ns_ldap_return_code
createSession(const ns_cred_t *auth, const char *serverAddr,
		    uint16_t port, int timeoutMilliSec,
		    LDAP **ldp, ns_ldap_error_t **errorp)
{
	int	useSSL = 0, gssapi = 0;
	char	errstr[MAXERROR];

	switch (auth->auth.type) {
		case NS_LDAP_AUTH_NONE:
		case NS_LDAP_AUTH_SIMPLE:
		case NS_LDAP_AUTH_SASL:
			break;
		case NS_LDAP_AUTH_TLS:
			useSSL = 1;
			break;
		default:
			(void) sprintf(errstr,
			    gettext("openConnection: unsupported "
			    "authentication method (%d)"), auth->auth.type);
			MKERROR(LOG_WARNING, *errorp,
			    LDAP_AUTH_METHOD_NOT_SUPPORTED, strdup(errstr),
			    NS_LDAP_MEMORY);
			return (NS_LDAP_INTERNAL);
	}

	if (port == USE_DEFAULT_PORT) {
		port = useSSL ? LDAPS_PORT : LDAP_PORT;
	}

	if (auth->auth.type == NS_LDAP_AUTH_SASL &&
	    auth->auth.saslmech == NS_LDAP_SASL_GSSAPI)
		gssapi = 1;

	if (useSSL)
		return (createTLSSession(auth, serverAddr, port,
		    timeoutMilliSec, ldp, errorp));
	else
		return (createNonTLSSession(serverAddr, port, gssapi,
		    ldp, errorp));
}

/*
 * This finction performs a non-SASL bind operation.  If an error accures,
 * the function returns an NS error code and provides an additional info
 * pointed by *errorp.
 */
static
ns_ldap_return_code
doSimpleBind(const ns_cred_t *auth,
		LDAP *ld,
		int timeoutSec,
		ns_ldap_error_t **errorp,
		int fail_if_new_pwd_reqd,
		int passwd_mgmt)
{
	char			*binddn, *passwd, errstr[MAXERROR], *errmsg;
	int			msgId, errnum = 0, ldap_rc;
	ns_ldap_return_code	ret_code;
	LDAPMessage		*resultMsg = NULL;
	LDAPControl		**controls;
	struct timeval		tv;

	binddn = auth->cred.unix_cred.userID;
	passwd = auth->cred.unix_cred.passwd;
	if (passwd == NULL || *passwd == '\0' ||
	    binddn == NULL || *binddn == '\0') {
		(void) sprintf(errstr, gettext("openConnection: "
		    "missing credentials for Simple bind"));
		MKERROR(LOG_WARNING, *errorp, LDAP_INVALID_CREDENTIALS,
		    strdup(errstr), NS_LDAP_MEMORY);
		(void) ldap_unbind(ld);
		return (NS_LDAP_INTERNAL);
	}

#ifdef DEBUG
	(void) fprintf(stderr, "tid= %d: +++Simple bind\n",
	    thr_self());
#endif /* DEBUG */
	msgId = ldap_simple_bind(ld, binddn, passwd);

	if (msgId == -1) {
		(void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER,
		    (void *)&errnum);
		(void) snprintf(errstr, sizeof (errstr),
		    gettext("openConnection: simple bind failed "
		    "- %s"), ldap_err2string(errnum));
		(void) ldap_unbind(ld);
		MKERROR(LOG_WARNING, *errorp, errnum, strdup(errstr),
		    NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	tv.tv_sec = timeoutSec;
	tv.tv_usec = 0;
	ldap_rc = ldap_result(ld, msgId, 0, &tv, &resultMsg);

	if ((ldap_rc == -1) || (ldap_rc == 0)) {
		(void) ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER,
		    (void *)&errnum);
		(void) snprintf(errstr, sizeof (errstr),
		    gettext("openConnection: simple bind failed "
		    "- %s"), ldap_err2string(errnum));
		(void) ldap_msgfree(resultMsg);
		(void) ldap_unbind(ld);
		MKERROR(LOG_WARNING, *errorp, errnum, strdup(errstr),
		    NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	/*
	 * get ldaprc, controls, and error msg
	 */
	ldap_rc = ldap_parse_result(ld, resultMsg, &errnum, NULL,
	    &errmsg, NULL, &controls, 1);

	if (ldap_rc != LDAP_SUCCESS) {
		(void) snprintf(errstr, sizeof (errstr),
		    gettext("openConnection: simple bind failed "
		    "- unable to parse result"));
		(void) ldap_unbind(ld);
		MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL,
		    strdup(errstr), NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	/* process the password management info, if any */
	ret_code = process_pwd_mgmt("simple",
	    errnum, controls, errmsg,
	    errorp,
	    fail_if_new_pwd_reqd,
	    passwd_mgmt);

	if (ret_code == NS_LDAP_INTERNAL) {
		(void) ldap_unbind(ld);
	}

	return (ret_code);
}

/*
 * This finction performs a SASL bind operation.  If an error accures,
 * the function returns an NS error code and provides an additional info
 * pointed by *errorp.
 */
static
ns_ldap_return_code
doSASLBind(const ns_cred_t *auth,
		LDAP *ld,
		int timeoutSec,
		ns_ldap_error_t **errorp,
		int fail_if_new_pwd_reqd,
		int passwd_mgmt)
{
	char			*binddn, *passwd, *digest_md5_name,
	    errstr[MAXERROR], *errmsg;
	struct berval		cred;
	int			ldap_rc, errnum = 0;
	ns_ldap_return_code	ret_code;
	struct timeval		tv;
	LDAPMessage		*resultMsg;
	LDAPControl		**controls;
	int			min_ssf = MIN_SASL_SSF, max_ssf = MAX_SASL_SSF;
	ns_sasl_cb_param_t	sasl_param;

	if (auth->auth.saslopt != NS_LDAP_SASLOPT_NONE &&
	    auth->auth.saslmech != NS_LDAP_SASL_GSSAPI) {
		(void) sprintf(errstr,
		    gettext("openConnection: SASL options are "
		    "not supported (%d) for non-GSSAPI sasl bind"),
		    auth->auth.saslopt);
		MKERROR(LOG_WARNING, *errorp,
		    LDAP_AUTH_METHOD_NOT_SUPPORTED,
		    strdup(errstr), NS_LDAP_MEMORY);
		(void) ldap_unbind(ld);
		return (NS_LDAP_INTERNAL);
	}
	if (auth->auth.saslmech != NS_LDAP_SASL_GSSAPI) {
		binddn = auth->cred.unix_cred.userID;
		passwd = auth->cred.unix_cred.passwd;
		if (passwd == NULL || *passwd == '\0' ||
		    binddn == NULL || *binddn == '\0') {
			(void) sprintf(errstr,
			gettext("openConnection: missing credentials "
			    "for SASL bind"));
			MKERROR(LOG_WARNING, *errorp,
			    LDAP_INVALID_CREDENTIALS,
			    strdup(errstr), NS_LDAP_MEMORY);
			(void) ldap_unbind(ld);
			return (NS_LDAP_INTERNAL);
		}
		cred.bv_val = passwd;
		cred.bv_len = strlen(passwd);
	}

	ret_code = NS_LDAP_SUCCESS;

	switch (auth->auth.saslmech) {
	case NS_LDAP_SASL_CRAM_MD5:
		/*
		 * NOTE: if iDS changes to support cram_md5,
		 * please add password management code here.
		 * Since ldap_sasl_cram_md5_bind_s does not
		 * return anything that could be used to
		 * extract the ldap rc/errmsg/control to
		 * determine if bind failed due to password
		 * policy, a new cram_md5_bind API will need
		 * to be introduced. See
		 * ldap_x_sasl_digest_md5_bind() and case
		 * NS_LDAP_SASL_DIGEST_MD5 below for details.
		 */
		if ((ldap_rc = ldap_sasl_cram_md5_bind_s(ld, binddn,
		    &cred, NULL, NULL)) != LDAP_SUCCESS) {
			(void) ldap_get_option(ld,
			    LDAP_OPT_ERROR_NUMBER, (void *)&errnum);
			(void) snprintf(errstr, sizeof (errstr),
			    gettext("openConnection: "
			    "sasl/CRAM-MD5 bind failed - %s"),
			    ldap_err2string(errnum));
			MKERROR(LOG_WARNING, *errorp, errnum,
			    strdup(errstr), NS_LDAP_MEMORY);
			(void) ldap_unbind(ld);
			return (NS_LDAP_INTERNAL);
		}
		break;
	case NS_LDAP_SASL_DIGEST_MD5:
		digest_md5_name = malloc(strlen(binddn) + 5);
		/* 5 = strlen("dn: ") + 1 */
		if (digest_md5_name == NULL) {
			(void) ldap_unbind(ld);
			return (NS_LDAP_MEMORY);
		}
		(void) strcpy(digest_md5_name, "dn: ");
		(void) strcat(digest_md5_name, binddn);

		tv.tv_sec = timeoutSec;
		tv.tv_usec = 0;
		ldap_rc = ldap_x_sasl_digest_md5_bind(ld,
		    digest_md5_name, &cred, NULL, NULL,
		    &tv, &resultMsg);

		if (resultMsg == NULL) {
			free(digest_md5_name);
			(void) ldap_get_option(ld,
			    LDAP_OPT_ERROR_NUMBER, (void *)&errnum);
			(void) snprintf(errstr, sizeof (errstr),
			    gettext("openConnection: "
			    "DIGEST-MD5 bind failed - %s"),
			    ldap_err2string(errnum));
			(void) ldap_unbind(ld);
			MKERROR(LOG_WARNING, *errorp, errnum,
			    strdup(errstr), NS_LDAP_MEMORY);
			return (NS_LDAP_INTERNAL);
		}

		/*
		 * get ldaprc, controls, and error msg
		 */
		ldap_rc = ldap_parse_result(ld, resultMsg, &errnum, NULL,
		    &errmsg, NULL, &controls, 1);

		if (ldap_rc != LDAP_SUCCESS) {
			free(digest_md5_name);
			(void) snprintf(errstr, sizeof (errstr),
			    gettext("openConnection: "
			    "DIGEST-MD5 bind failed "
			    "- unable to parse result"));
			(void) ldap_unbind(ld);
			MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL,
			    strdup(errstr), NS_LDAP_MEMORY);
			return (NS_LDAP_INTERNAL);
		}

		/* process the password management info, if any */
		ret_code = process_pwd_mgmt("sasl/DIGEST-MD5",
		    errnum, controls, errmsg,
		    errorp,
		    fail_if_new_pwd_reqd,
		    passwd_mgmt);

		if (ret_code == NS_LDAP_INTERNAL) {
			(void) ldap_unbind(ld);
		}

		free(digest_md5_name);
		break;
	case NS_LDAP_SASL_GSSAPI:
		if (sasl_gssapi_inited == 0) {
			ret_code = __s_api_sasl_gssapi_init();
			if (ret_code != NS_LDAP_SUCCESS) {
				(void) snprintf(errstr, sizeof (errstr),
				    gettext("openConnection: "
				    "GSSAPI initialization "
				    "failed"));
				(void) ldap_unbind(ld);
				MKERROR(LOG_WARNING, *errorp, ret_code,
				    strdup(errstr), NS_LDAP_MEMORY);
				return (ret_code);
			}
		}
		(void) memset(&sasl_param, 0,
		    sizeof (ns_sasl_cb_param_t));
		sasl_param.authid = NULL;
		sasl_param.authzid = "";
		(void) ldap_set_option(ld, LDAP_OPT_X_SASL_SSF_MIN,
		    (void *)&min_ssf);
		(void) ldap_set_option(ld, LDAP_OPT_X_SASL_SSF_MAX,
		    (void *)&max_ssf);

		ldap_rc = ldap_sasl_interactive_bind_s(
		    ld, NULL, "GSSAPI",
		    NULL, NULL, LDAP_SASL_INTERACTIVE,
		    __s_api_sasl_bind_callback,
		    &sasl_param);

		if (ldap_rc != LDAP_SUCCESS) {
			(void) snprintf(errstr, sizeof (errstr),
			    gettext("openConnection: "
			    "GSSAPI bind failed "
			    "- %d %s"),
			    ldap_rc,
			    ldap_err2string(ldap_rc));
			(void) ldap_unbind(ld);
			MKERROR(LOG_WARNING, *errorp, NS_LDAP_INTERNAL,
			    strdup(errstr), NS_LDAP_MEMORY);
			return (NS_LDAP_INTERNAL);
		}

		break;
	default:
		(void) ldap_unbind(ld);
		(void) sprintf(errstr,
		    gettext("openConnection: unsupported SASL "
		    "mechanism (%d)"), auth->auth.saslmech);
		MKERROR(LOG_WARNING, *errorp,
		    LDAP_AUTH_METHOD_NOT_SUPPORTED, strdup(errstr),
		    NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	return (ret_code);
}

/*
 * This function performs an LDAP Bind operation proceeding
 * from a type of the connection specified by auth->auth.type.
 *
 * INPUT:
 *     auth - a structure specified an authenticastion method and credentials,
 *     ld - a pointer returned by the createSession() function,
 *     timeoutSec - a timeout in seconds for the Bind operation,
 *     fail_if_new_pwd_reqd - a flag indicating that the call should fail
 *                            if a new password is required,
 *     passwd_mgmt - a flag indicating that the server supports
 *                   password management.
 *
 * OUTPUT:
 *     If an error accures, the function returns an NS error code
 *     and provides an additional info pointed by *errorp.
 */
static
ns_ldap_return_code
performBind(const ns_cred_t *auth,
		LDAP *ld,
		int timeoutSec,
		ns_ldap_error_t **errorp,
		int fail_if_new_pwd_reqd,
		int passwd_mgmt)
{
	int	bindType;
	char	errstr[MAXERROR];

	ns_ldap_return_code (*binder)(const ns_cred_t *auth,
	    LDAP *ld,
	    int timeoutSec,
	    ns_ldap_error_t **errorp,
	    int fail_if_new_pwd_reqd,
	    int passwd_mgmt) = NULL;

	if (!ld) {
		(void) sprintf(errstr,
		    "performBind: LDAP session "
		    "is not initialized.");
		MKERROR(LOG_WARNING, *errorp,
		    LDAP_AUTH_METHOD_NOT_SUPPORTED,
		    strdup(errstr), NS_LDAP_MEMORY);
		return (NS_LDAP_INTERNAL);
	}

	bindType = auth->auth.type == NS_LDAP_AUTH_TLS ?
	    auth->auth.tlstype : auth->auth.type;

	switch (bindType) {
		case NS_LDAP_AUTH_NONE:
#ifdef DEBUG
		(void) fprintf(stderr, "tid= %d: +++Anonymous bind\n",
		    thr_self());
#endif /* DEBUG */
			break;
		case NS_LDAP_AUTH_SIMPLE:
			binder = doSimpleBind;
			break;
		case NS_LDAP_AUTH_SASL:
			binder = doSASLBind;
			break;
		default:
			(void) sprintf(errstr,
			    gettext("openConnection: unsupported "
			    "authentication method "
			    "(%d)"), bindType);
			MKERROR(LOG_WARNING, *errorp,
			    LDAP_AUTH_METHOD_NOT_SUPPORTED,
			    strdup(errstr), NS_LDAP_MEMORY);
			(void) ldap_unbind(ld);
			return (NS_LDAP_INTERNAL);
	}

	if (binder != NULL) {
		return (*binder)(auth,
		    ld,
		    timeoutSec,
		    errorp,
		    fail_if_new_pwd_reqd,
		    passwd_mgmt);
	}

	return (NS_LDAP_SUCCESS);
}