ns_connmgmt.c revision 6842:e0ac183a8194
1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21/*
22 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26#pragma ident	"%Z%%M%	%I%	%E% SMI"
27
28#include <string.h>
29#include <errno.h>
30#include <syslog.h>
31#include <procfs.h>
32#include <unistd.h>
33#include <fcntl.h>
34#include <libintl.h>
35#include <atomic.h>
36#include <pthread.h>
37#include <sys/mman.h>
38#include <time.h>
39#include "solaris-int.h"
40#include "ns_connmgmt.h"
41#include "ns_cache_door.h"
42#include "ns_internal.h"
43
44/*
45 * Access (reference, shutdown, or reload) the current connection
46 * management control structure conn_mgmt_t.
47 */
48#define	NS_CONN_MGMT_OP_REF		1
49#define	NS_CONN_MGMT_OP_SHUTDOWN	2
50#define	NS_CONN_MGMT_OP_RELOAD_CONFIG	3
51#define	NS_CONN_MGMT_OP_NEW_CONFIG	4
52#define	NS_CONN_MGMT_OP_LIB_INIT	5
53
54static ns_conn_mgmt_t *access_conn_mgmt(int);
55static ns_conn_mgmt_t *release_conn_mgmt(ns_conn_mgmt_t *, boolean_t);
56static int close_conn_mt(ns_conn_mt_t *, int, ns_ldap_error_t **,
57	ns_conn_user_t *);
58static int close_conn_mt_when_nouser(ns_conn_mt_t *cm);
59void shutdown_all_conn_mt(ns_conn_mgmt_t *cmg);
60static int conn_signal(ns_conn_mt_t *);
61static int conn_wait(ns_conn_mt_t *, ns_conn_user_t *);
62static void close_conn_mt_by_procchg(ns_conn_mt_t *cm, int rc, char *errmsg);
63static ns_conn_mgmt_t *proc_server_change(ns_server_status_change_t *chg,
64	ns_conn_mgmt_t  *cmg);
65static void get_preferred_servers(boolean_t, boolean_t, ns_conn_mgmt_t *);
66static void start_thread();
67
68static ns_conn_mgmt_t	*ns_connmgmt = NULL;
69static ns_conn_mgmt_t	*ns_connmgmt_parent = NULL;
70static mutex_t		ns_connmgmt_lock = DEFAULTMUTEX;
71static boolean_t	ns_connmgmt_shutting_down = B_FALSE;
72
73#define	NS_CONN_MSG_NO_CONN_MGMT gettext( \
74	"libsldap: unable to allocate the connection management control")
75#define	NS_CONN_MSG_NO_MTC_KEY gettext( \
76	"libsldap: unable to allocate the TSD key for per-thread ldap error")
77#define	NS_CONN_MSG_NO_CMG_KEY gettext( \
78	"libsldap: unable to allocate the TSD key for connection management")
79#define	NS_CONN_MSG_SHUTDOWN gettext("libsldap: library is being unloaded")
80#define	NS_CONN_MSG_RELOADED gettext( \
81	"libsldap: configuration has been reloaded")
82#define	NS_CONN_MSG_SHUTDOWN_RELOADED gettext( \
83	"libsldap: library unloaded or configuration has been reloaded")
84#define	NS_CONN_MSG_BAD_CACHEMGR_DATA gettext( \
85	"libsldap: received incorrect data from ldap_cachemgr")
86#define	NS_CONN_MSG_MEMORY_ERROR gettext( \
87	"libsldap: unable to allocate memory")
88#define	NS_CONN_MSG_NO_PROCCHG_THREAD gettext( \
89	"libsldap: unable to start the server monitor thread (%s)")
90#define	NS_CONN_MSG_DOWN_FROM_CACHEMGR gettext( \
91	"libsldap: server down reported by ldap_cachemgr")
92
93static int ns_conn_free = 1;
94#define	NS_CONN_UNLOCK_AND_FREE(free, cm, cmg)  \
95{ \
96	(void) mutex_unlock(&(cm)->lock);	\
97	if (free == 1)	\
98		cmg = free_conn_mt((cm), 1); \
99	if (cmg != NULL) \
100		(void) mutex_unlock(&(cmg)->lock); \
101}
102
103#define	NS_CONN_CHECK_ABORT_AND_LOCK(cmg, cu, errp) \
104{ \
105	char *msg = NULL; \
106	(void) mutex_lock(&(cmg)->lock); \
107	if ((cmg)->shutting_down == B_TRUE) \
108		msg = NS_CONN_MSG_SHUTDOWN; \
109	else if ((cmg)->cfg_reloaded == B_TRUE)  \
110		msg = NS_CONN_MSG_RELOADED; \
111	if (msg != NULL) { \
112		(*errp) = __s_api_make_error(NS_LDAP_OP_FAILED, msg); \
113		(void) mutex_unlock(&(cmg)->lock); \
114		return (NS_LDAP_OP_FAILED); \
115	} \
116}
117
118/*
119 * TSD keys ns_mtckey and ns_cmgkey are for sharing ldap connections
120 * and their associated connection management structure among
121 * multiple threads. The pointers to the per-thread ldap error
122 * information and the connection management structure are
123 * saved in ns_mtckey and ns_cmgkey.
124 */
125thread_key_t ns_mtckey = THR_ONCE_KEY;
126thread_key_t ns_cmgkey = THR_ONCE_KEY;
127
128/* Per thread LDAP error resides in thread-specific data (ns_mtckey) */
129struct ldap_error {
130	int	le_errno;
131	char	*le_matched;
132	char	*le_errmsg;
133};
134
135/* NULL struct ldap_error */
136static struct ldap_error ldap_error_NULL = { LDAP_SUCCESS, NULL, NULL};
137
138/* destructor: free the ldap error data in the thread specific area */
139static void
140ns_mtckey_cleanup(void *key) {
141	struct ldap_error *le = (struct ldap_error *)key;
142
143	if (le == NULL)
144		return;
145	if (le->le_matched != NULL) {
146		ldap_memfree(le->le_matched);
147	}
148	if (le->le_errmsg != NULL) {
149		ldap_memfree(le->le_errmsg);
150	}
151	free(le);
152}
153
154/* Free/detach the thread specific data structures */
155static void
156conn_tsd_free() {
157	void	*tsd = NULL;
158	int	rc;
159
160	/* free the per-thread ldap error info */
161	rc = thr_getspecific(ns_mtckey, &tsd);
162	if (rc == 0 && tsd != NULL)
163		ns_mtckey_cleanup(tsd);
164	(void) thr_setspecific(ns_mtckey, NULL);
165
166	/* detach the connection management control */
167	(void) thr_setspecific(ns_cmgkey, NULL);
168}
169
170/* per-thread callback function for allocating a mutex */
171static void *
172ns_mutex_alloc(void)
173{
174	mutex_t *mutexp = NULL;
175
176	if ((mutexp = malloc(sizeof (mutex_t))) != NULL) {
177		if (mutex_init(mutexp, USYNC_THREAD, NULL) != 0) {
178			free(mutexp);
179			mutexp = NULL;
180		}
181	}
182	return (mutexp);
183}
184
185/* per-thread callback function for freeing a mutex */
186static void
187ns_mutex_free(void *mutexp)
188{
189	(void) mutex_destroy((mutex_t *)mutexp);
190	free(mutexp);
191}
192
193/*
194 * Function for setting up thread-specific data
195 * where per thread LDAP error and the pointer
196 * to the active connection management control
197 * are stored.
198 */
199static int
200conn_tsd_setup(ns_conn_mgmt_t *cmg)
201{
202	void	*tsd;
203	int	rc;
204
205	rc = thr_setspecific(ns_cmgkey, cmg);
206	if (rc != 0) /* must be ENOMEM */
207		return (-1);
208
209	/* return success if the ns_mtckey TSD is already set */
210	rc = thr_getspecific(ns_mtckey, &tsd);
211	if (rc == 0 && tsd != NULL)
212		return (0);
213
214	/* allocate and set the ns_mtckey TSD */
215	tsd = (void *) calloc(1, sizeof (struct ldap_error));
216	if (tsd == NULL)
217		return (-1);
218	rc = thr_setspecific(ns_mtckey, tsd);
219	if (rc != 0) { /* must be ENOMEM */
220		free(tsd);
221		return (-1);
222	}
223	return (0);
224}
225
226/* Callback function for setting the per thread LDAP error */
227/*ARGSUSED*/
228static void
229set_ld_error(int err, char *matched, char *errmsg, void *dummy)
230{
231	struct ldap_error	*le;
232	int			eno;
233
234	if ((eno = thr_getspecific(ns_mtckey, (void **)&le)) != 0) {
235		syslog(LOG_ERR, gettext(
236		    "libsldap: set_ld_error: thr_getspecific failed (%s)."),
237		    strerror(eno));
238		return;
239	}
240
241	/* play safe, do nothing if TSD pointer is NULL */
242	if (le == NULL) {
243		syslog(LOG_INFO, gettext(
244		    "libsldap: set_ld_error: TSD pointer is NULL."));
245		return;
246	}
247
248	le->le_errno = err;
249
250	if (le->le_matched != NULL) {
251		ldap_memfree(le->le_matched);
252		le->le_matched = NULL;
253	}
254	le->le_matched = matched;
255
256	if (le->le_errmsg != NULL) {
257		ldap_memfree(le->le_errmsg);
258		le->le_errmsg = NULL;
259	}
260	le->le_errmsg = errmsg;
261}
262
263/* check and allocate the thread-specific data for using a MT connection */
264static int
265conn_tsd_check(ns_conn_mgmt_t *cmg)
266{
267	if (conn_tsd_setup(cmg) != 0)
268		return (NS_LDAP_MEMORY);
269
270	return (NS_LDAP_SUCCESS);
271}
272
273/* Callback function for getting the per thread LDAP error */
274/*ARGSUSED*/
275static int
276get_ld_error(char **matched, char **errmsg, void *dummy)
277{
278	struct ldap_error	*le;
279	int			eno;
280
281	if ((eno = thr_getspecific(ns_mtckey, (void **)&le)) != 0) {
282		syslog(LOG_ERR, gettext(
283		    "libsldap: get_ld_error: thr_getspecific failed (%s)"),
284		    strerror(eno));
285		return (eno);
286	}
287
288	/* play safe, return NULL error data, if TSD pointer is NULL */
289	if (le == NULL)
290		le = &ldap_error_NULL;
291
292	if (matched != NULL) {
293		*matched = le->le_matched;
294	}
295	if (errmsg != NULL) {
296		*errmsg = le->le_errmsg;
297	}
298	return (le->le_errno);
299}
300
301/* Callback function for setting per thread errno */
302static void
303set_errno(int err)
304{
305	errno = err;
306}
307
308/* Callback function for getting per thread errno */
309static int
310get_errno(void)
311{
312	return (errno);
313}
314
315/* set up an ldap session 'ld' for sharing among multiple threads */
316static int
317setup_mt_conn(LDAP *ld)
318{
319
320	struct ldap_thread_fns		tfns;
321	struct ldap_extra_thread_fns	extrafns;
322	int				rc;
323
324	/*
325	 * Set the function pointers for dealing with mutexes
326	 * and error information
327	 */
328	(void) memset(&tfns, '\0', sizeof (struct ldap_thread_fns));
329	tfns.ltf_mutex_alloc = (void *(*)(void)) ns_mutex_alloc;
330	tfns.ltf_mutex_free = (void (*)(void *)) ns_mutex_free;
331	tfns.ltf_mutex_lock = (int (*)(void *)) mutex_lock;
332	tfns.ltf_mutex_unlock = (int (*)(void *)) mutex_unlock;
333	tfns.ltf_get_errno = get_errno;
334	tfns.ltf_set_errno = set_errno;
335	tfns.ltf_get_lderrno = get_ld_error;
336	tfns.ltf_set_lderrno = set_ld_error;
337	tfns.ltf_lderrno_arg = NULL;
338
339	/*
340	 * Set up the ld to use those function pointers
341	 */
342	rc = ldap_set_option(ld, LDAP_OPT_THREAD_FN_PTRS,
343	    (void *) &tfns);
344	if (rc < 0) {
345		syslog(LOG_INFO, gettext("libsldap: ldap_set_option "
346		"(LDAP_OPT_THREAD_FN_PTRS)"));
347		return (0);
348	}
349
350	/*
351	 * Set the function pointers for working with semaphores
352	 */
353	(void) memset(&extrafns, '\0',
354	    sizeof (struct ldap_extra_thread_fns));
355	extrafns.ltf_threadid_fn = (void * (*)(void))thr_self;
356	extrafns.ltf_mutex_trylock = NULL;
357	extrafns.ltf_sema_alloc = NULL;
358	extrafns.ltf_sema_free = NULL;
359	extrafns.ltf_sema_wait = NULL;
360	extrafns.ltf_sema_post = NULL;
361
362	/* Set up the ld to use those function pointers */
363	rc = ldap_set_option(ld, LDAP_OPT_EXTRA_THREAD_FN_PTRS,
364	    (void *) &extrafns);
365	if (rc < 0) {
366		syslog(LOG_INFO, gettext("libsldap: ldap_set_option "
367		"(LDAP_OPT_EXTRA_THREAD_FN_PTRS)"));
368		return (0);
369	}
370
371	return (1);
372}
373
374/* set up an MT connection for sharing among multiple threads */
375static int
376setup_mt_ld(LDAP *ld, ns_conn_mgmt_t *cmg)
377{
378	thread_t	t = thr_self();
379
380	/* set up the per-thread data for using the MT connection */
381	if (conn_tsd_setup(cmg) == -1) {
382		syslog(LOG_WARNING,
383		    gettext("libsldap: tid= %d: unable to set up TSD\n"), t);
384		return (-1);
385	}
386
387	if (setup_mt_conn(ld) == 0) {
388		/* multiple threads per connection not supported */
389		syslog(LOG_WARNING, gettext("libsldap: tid= %d: multiple "
390		    "threads per connection not supported\n"), t);
391		conn_tsd_free();
392		return (-1);
393	}
394	return (0);
395}
396
397/*
398 * Check name and UID of process, if it is nscd.
399 *
400 * Input:
401 *   pid	: PID of checked process
402 *   check_uid	: check if UID == 0
403 * Output:
404 *   B_TRUE	: nscd detected
405 *   B_FALSE	: nscd not confirmed
406 */
407static boolean_t
408check_nscd_proc(pid_t pid, boolean_t check_uid)
409{
410	psinfo_t	pinfo;
411	char		fname[MAXPATHLEN];
412	ssize_t		ret;
413	int		fd;
414
415	if (snprintf(fname, MAXPATHLEN, "/proc/%d/psinfo", pid) > 0) {
416		if ((fd = open(fname,  O_RDONLY)) >= 0) {
417			ret = read(fd, &pinfo, sizeof (psinfo_t));
418			(void) close(fd);
419			if ((ret == sizeof (psinfo_t)) &&
420			    (strcmp(pinfo.pr_fname, "nscd") == 0)) {
421				if (check_uid && (pinfo.pr_uid != 0))
422					return (B_FALSE);
423				return (B_TRUE);
424			}
425		}
426	}
427	return (B_FALSE);
428}
429
430/*
431 * Check if this process is peruser nscd.
432 */
433boolean_t
434__s_api_peruser_proc(void)
435{
436	pid_t		my_ppid;
437	static mutex_t	nscdLock = DEFAULTMUTEX;
438	static pid_t	checkedPpid = (pid_t)-1;
439	static boolean_t isPeruserNscd = B_FALSE;
440
441	my_ppid = getppid();
442
443	/*
444	 * Already checked before for this process? If yes, return cached
445	 * response.
446	 */
447	if (my_ppid == checkedPpid) {
448		return (isPeruserNscd);
449	}
450
451	(void) mutex_lock(&nscdLock);
452
453	/* Check once more incase another thread has just complete this. */
454	if (my_ppid == checkedPpid) {
455		(void) mutex_unlock(&nscdLock);
456		return (isPeruserNscd);
457	}
458
459	/* Reinitialize to be sure there is no residue after fork. */
460	isPeruserNscd = B_FALSE;
461
462	/* Am I the nscd process? */
463	if (check_nscd_proc(getpid(), B_FALSE)) {
464		/* Is my parent the nscd process with UID == 0. */
465		isPeruserNscd = check_nscd_proc(my_ppid, B_TRUE);
466	}
467
468	/* Remeber for whom isPeruserNscd is. */
469	checkedPpid = my_ppid;
470
471	(void) mutex_unlock(&nscdLock);
472	return (isPeruserNscd);
473}
474
475/*
476 * Check if this process is main nscd.
477 */
478boolean_t
479__s_api_nscd_proc(void)
480{
481	pid_t		my_pid;
482	static mutex_t	nscdLock = DEFAULTMUTEX;
483	static pid_t	checkedPid = (pid_t)-1;
484	static boolean_t isMainNscd = B_FALSE;
485
486	/*
487	 * Don't bother checking if this process isn't root, this cannot
488	 * be main nscd.
489	 */
490	if (getuid() != 0)
491		return (B_FALSE);
492
493	my_pid = getpid();
494
495	/*
496	 * Already checked before for this process? If yes, return cached
497	 * response.
498	 */
499	if (my_pid == checkedPid) {
500		return (isMainNscd);
501	}
502
503	(void) mutex_lock(&nscdLock);
504
505	/* Check once more incase another thread has just done this. */
506	if (my_pid == checkedPid) {
507		(void) mutex_unlock(&nscdLock);
508		return (isMainNscd);
509	}
510
511	/*
512	 * Am I the nscd process? UID is already checked, not needed from
513	 * psinfo.
514	 */
515	isMainNscd = check_nscd_proc(my_pid, B_FALSE);
516
517	/* Remeber for whom isMainNscd is. */
518	checkedPid = my_pid;
519
520	(void) mutex_unlock(&nscdLock);
521	return (isMainNscd);
522}
523
524/*
525 * initialize a connection management control structure conn_mgmt_t
526 */
527ns_conn_mgmt_t *
528init_conn_mgmt()
529{
530	ns_conn_mgmt_t	*cmg;
531
532	cmg = (ns_conn_mgmt_t *)calloc(1, sizeof (*cmg));
533	if (cmg == NULL) {
534		syslog(LOG_ERR, NS_CONN_MSG_NO_CONN_MGMT);
535		return (NULL);
536	}
537
538	/* is this process nscd or peruser nscd ? */
539	cmg->is_nscd = __s_api_nscd_proc();
540	cmg->is_peruser_nscd = __s_api_peruser_proc();
541
542	/*
543	 * assume the underlying libldap allows multiple threads sharing
544	 * the same ldap connection (MT connection)
545	 */
546	cmg->ldap_mt = B_TRUE;
547	/* state is inactive until MT connection is required/requested */
548	cmg->state = NS_CONN_MGMT_INACTIVE;
549
550	(void) mutex_init(&cmg->lock, USYNC_THREAD, NULL);
551	(void) mutex_init(&cmg->cfg_lock, USYNC_THREAD, NULL);
552	cmg->pid = getpid();
553
554	/* for nscd or peruser nscd, MT connection is required */
555	if (cmg->is_nscd == B_TRUE || cmg->is_peruser_nscd == B_TRUE)
556		cmg->state = NS_CONN_MGMT_ACTIVE;
557
558	/*
559	 * reference (or initialize) the current Native LDAP configuration and
560	 * if in nscd process, make it never refreshed
561	 */
562	cmg->config = __s_api_get_default_config_global();
563	if (cmg->config == NULL)
564		cmg->config = __s_api_loadrefresh_config_global();
565	if (cmg->config != NULL) {
566		/*
567		 * main nscd get config change notice from ldap_cachemgr
568		 * so won't times out and refresh the config
569		 */
570		if (cmg->is_nscd  == B_TRUE)
571			(cmg->config)->paramList[NS_LDAP_EXP_P].ns_tm = 0;
572		cmg->cfg_cookie = cmg->config->config_cookie;
573	}
574
575	return (cmg);
576}
577
578static void
579mark_shutdown_or_reoladed(int op)
580{
581	ns_conn_mgmt_t	*cmg = ns_connmgmt;
582
583	(void) mutex_lock(&cmg->lock);
584	if (op == NS_CONN_MGMT_OP_SHUTDOWN)
585		cmg->shutting_down = B_TRUE;
586	else
587		cmg->cfg_reloaded = B_TRUE;
588	atomic_inc_uint(&cmg->ref_cnt);
589	cmg->state = NS_CONN_MGMT_DETACHED;
590
591	if (op == NS_CONN_MGMT_OP_RELOAD_CONFIG)
592		__s_api_init_config_global(NULL);
593
594	(void) mutex_unlock(&cmg->lock);
595}
596
597/*
598 * Return a pointer to the current connection management. If
599 * it has not been created, or is requested to recreate, then
600 * create and return the pointer. It is possible, the current
601 * one is created by the parent before fork, create a new
602 * one too in such a case.
603 */
604static ns_conn_mgmt_t *
605get_current_conn_mgmt(int op)
606{
607	ns_conn_mgmt_t	*cmg = ns_connmgmt;
608	static pid_t	checked_pid = (pid_t)-1;
609	pid_t		mypid;
610
611	mypid = getpid();
612	if (cmg == NULL || checked_pid != mypid) {
613		checked_pid = mypid;
614
615		/*
616		 * if current conn_mgmt not created yet or is from parent
617		 * or is requested to recreate, create it
618		 */
619		if (cmg == NULL || cmg->pid != mypid) {
620			if (cmg != NULL) {
621				/*
622				 * We don't want to free the conn_mgmt
623				 * allocated by the parent, since
624				 * there may be ldap connections
625				 * still being used. So leave it
626				 * alone but keep it referenced,
627				 * so that it will not be flagged
628				 * as a piece of leaked memory.
629				 */
630				ns_connmgmt_parent = cmg;
631				/*
632				 * avoid lint warning; does not
633				 * change the conn_mgmt in parent
634				 */
635				ns_connmgmt_parent->state =
636				    NS_CONN_MGMT_DETACHED;
637			}
638			ns_connmgmt = init_conn_mgmt();
639			cmg = ns_connmgmt;
640			/*
641			 * ensure it will not be destroyed until explicitly
642			 * shut down or reloaded
643			 */
644			if (op == NS_CONN_MGMT_OP_REF)
645				atomic_inc_uint(&cmg->ref_cnt);
646		}
647	}
648
649	return (cmg);
650}
651
652static ns_conn_mgmt_t *
653access_conn_mgmt(int op)
654{
655	ns_conn_mgmt_t	*cmg = NULL;
656	ns_conn_mgmt_t	*cmg_prev;
657
658	(void) mutex_lock(&ns_connmgmt_lock);
659
660	/*
661	 * connection management is not available when the libsldap is being
662	 * unloaded or shut down
663	 */
664	if (ns_connmgmt_shutting_down == B_TRUE) {
665		(void) mutex_unlock(&ns_connmgmt_lock);
666		return (NULL);
667	}
668
669	if (op == NS_CONN_MGMT_OP_SHUTDOWN) {
670		ns_connmgmt_shutting_down = B_TRUE;
671		if (ns_connmgmt != NULL) {
672			cmg = ns_connmgmt;
673			mark_shutdown_or_reoladed(op);
674			ns_connmgmt = NULL;
675		}
676		(void) mutex_unlock(&ns_connmgmt_lock);
677		return (cmg);
678	}
679
680	if (op == NS_CONN_MGMT_OP_RELOAD_CONFIG ||
681	    op == NS_CONN_MGMT_OP_NEW_CONFIG) {
682		cmg_prev = ns_connmgmt;
683		mark_shutdown_or_reoladed(op);
684		/*
685		 * the previous cmg (cmg_prev) will be freed later
686		 * when its ref count reaches zero
687		 */
688		ns_connmgmt = NULL;
689	}
690
691	cmg = get_current_conn_mgmt(op);
692	if (cmg == NULL) {
693		(void) mutex_unlock(&ns_connmgmt_lock);
694		return (NULL);
695	}
696
697	atomic_inc_uint(&cmg->ref_cnt);
698	if (op == NS_CONN_MGMT_OP_RELOAD_CONFIG ||
699	    op == NS_CONN_MGMT_OP_NEW_CONFIG)
700		cmg = cmg_prev;
701	else { /* op is  NS_CONN_MGMT_OP_REF  or NS_CONN_MGMT_OP_LIB_INIT */
702		if (cmg->config == NULL)
703			cmg->config = __s_api_get_default_config();
704	}
705
706	(void) mutex_unlock(&ns_connmgmt_lock);
707	return (cmg);
708}
709
710/*
711 * free a connection management control
712 */
713static void
714free_conn_mgmt(ns_conn_mgmt_t *cmg)
715{
716	union {
717		ldap_data_t	s_d;
718		char		s_b[1024];
719	} space;
720	ldap_data_t	*sptr;
721	int		ndata;
722	int		adata;
723	int		rc;
724	ldap_get_chg_cookie_t cookie;
725
726	if (cmg == NULL)
727		return;
728	cookie = cmg->cfg_cookie;
729
730	__s_api_free2dArray(cmg->pservers);
731	/* destroy the previous config or release the current one */
732	if (cmg->config != NULL) {
733		if (cmg->state == NS_CONN_MGMT_DETACHED)
734			__s_api_destroy_config(cmg->config);
735		else
736			__s_api_release_config(cmg->config);
737	}
738
739	/* stop the server status/config-change monitor thread */
740	if (cmg->procchg_started == B_TRUE) {
741		if (cmg->procchg_tid != thr_self()) {
742			if (cmg->procchg_door_call == B_TRUE) {
743				adata = sizeof (ldap_call_t) + 1;
744				ndata = sizeof (space);
745				space.s_d.ldap_call.ldap_callnumber =
746				    GETSTATUSCHANGE;
747				space.s_d.ldap_call.ldap_u.get_change.op =
748				    NS_STATUS_CHANGE_OP_STOP;
749				space.s_d.ldap_call.ldap_u.get_change.cookie =
750				    cookie;
751				sptr = &space.s_d;
752				rc = __ns_ldap_trydoorcall(&sptr, &ndata,
753				    &adata);
754				if (rc != NS_CACHE_SUCCESS)
755					syslog(LOG_INFO,
756					    gettext("libsldap: "
757					    "free_conn_mgmt():"
758					    " stopping door call "
759					    " GETSTATUSCHANGE failed "
760					    " (rc = %d)"), rc);
761			}
762			(void) pthread_cancel(cmg->procchg_tid);
763			cmg->procchg_started = B_FALSE;
764		}
765	}
766
767	free(cmg);
768}
769
770static ns_conn_mgmt_t *
771release_conn_mgmt(ns_conn_mgmt_t *cmg, boolean_t unlock_cmg)
772{
773	if (cmg == NULL)
774		return (NULL);
775	if (atomic_dec_uint_nv(&cmg->ref_cnt) == 0) {
776		if (cmg->state == NS_CONN_MGMT_DETACHED) {
777			if (unlock_cmg == B_TRUE)
778				(void) mutex_unlock(&cmg->lock);
779			free_conn_mgmt(cmg);
780			return (NULL);
781		} else {
782			syslog(LOG_WARNING,
783			    gettext("libsldap: connection management "
784			    " has a refcount of zero but the state "
785			    " is not DETACHED (%d)"), cmg->state);
786			cmg = NULL;
787		}
788	}
789	return (cmg);
790}
791
792/*
793 * exposed function for initializing a connection management control structure
794 */
795ns_conn_mgmt_t *
796__s_api_conn_mgmt_init()
797{
798	if (thr_keycreate_once(&ns_mtckey, ns_mtckey_cleanup) != 0) {
799		syslog(LOG_WARNING, NS_CONN_MSG_NO_MTC_KEY);
800		return (NULL);
801	}
802
803	if (thr_keycreate_once(&ns_cmgkey, NULL) != 0) {
804		syslog(LOG_WARNING, NS_CONN_MSG_NO_CMG_KEY);
805		return (NULL);
806	}
807
808	return (access_conn_mgmt(NS_CONN_MGMT_OP_LIB_INIT));
809}
810
811/* initialize a connection user */
812ns_conn_user_t *
813__s_api_conn_user_init(int type, void *userinfo, boolean_t referral)
814{
815	ns_conn_user_t	*cu;
816	ns_conn_mgmt_t	*cmg;
817
818	/* delete the reference to the previously used conn_mgmt */
819	(void) thr_setspecific(ns_cmgkey, NULL);
820
821	cmg = access_conn_mgmt(NS_CONN_MGMT_OP_REF);
822	if (cmg == NULL)
823		return (NULL);
824
825	if (cmg->state != NS_CONN_MGMT_ACTIVE &&
826	    cmg->state != NS_CONN_MGMT_INACTIVE) {
827		atomic_dec_uint(&cmg->ref_cnt);
828		return (NULL);
829	}
830
831	cu = (ns_conn_user_t *)calloc(1, sizeof (*cu));
832	if (cu == NULL) {
833		atomic_dec_uint(&cmg->ref_cnt);
834		return (NULL);
835	}
836
837	cu->type = type;
838	cu->state = NS_CONN_USER_ALLOCATED;
839	cu->tid = thr_self();
840	cu->userinfo = userinfo;
841	cu->referral = referral;
842	cu->ns_rc = NS_LDAP_SUCCESS;
843	cu->conn_mgmt = cmg;
844
845	(void) conn_tsd_setup(cmg);
846
847	return (cu);
848}
849
850/*
851 * Free the resources used by a connection user.
852 * The caller should ensure this conn_user is
853 * not associated with any conn_mt, i.e.,
854 * not in any conn_mt's linked list of conn_users.
855 * The caller needs to free the userinfo member
856 * as well.
857 */
858void
859__s_api_conn_user_free(ns_conn_user_t *cu)
860{
861	ns_conn_mgmt_t	*cmg;
862
863	if (cu == NULL)
864		return;
865
866	cu->state = NS_CONN_USER_FREED;
867	if (cu->ns_error != NULL)
868		(void) __ns_ldap_freeError(&cu->ns_error);
869
870	cmg = cu->conn_mgmt;
871	conn_tsd_free();
872	(void) release_conn_mgmt(cmg, B_FALSE);
873	(void) free(cu);
874}
875
876/*
877 * Initialize an MT connection control structure
878 * that will be used to represent an ldap connection
879 * to be shared among multiple threads and to hold
880 * and manage all the conn_users using the ldap
881 * connection.
882 */
883static ns_conn_mt_t *
884init_conn_mt(ns_conn_mgmt_t *cmg, ns_ldap_error_t **ep)
885{
886	ns_conn_mt_t	*cm;
887	ns_conn_mgmt_t	*cmg_a;
888
889	cm = (ns_conn_mt_t *)calloc(1, sizeof (*cm));
890	if (cm == NULL) {
891		if (ep != NULL)
892			*ep = __s_api_make_error(NS_LDAP_MEMORY, NULL);
893		return (NULL);
894	}
895
896	cmg_a = access_conn_mgmt(NS_CONN_MGMT_OP_REF);
897	if (cmg_a != cmg) {
898		if (cmg_a != NULL) {
899			(void) release_conn_mgmt(cmg_a, B_FALSE);
900			if (ep != NULL)
901				*ep = __s_api_make_error(NS_LDAP_OP_FAILED,
902				    NS_CONN_MSG_SHUTDOWN_RELOADED);
903		}
904		return (NULL);
905	}
906
907	(void) mutex_init(&cm->lock, USYNC_THREAD, NULL);
908	cm->state = NS_CONN_MT_CONNECTING;
909	cm->tid = thr_self();
910	cm->pid = getpid();
911	cm->next = NULL;
912	cm->cu_head = NULL;
913	cm->cu_tail = NULL;
914	cm->conn = NULL;
915	cm->conn_mgmt = cmg;
916
917	return (cm);
918}
919
920/*
921 * Free an MT connection control structure, assume conn_mgmt is locked.
922 * 'unlock_cmg' is passed to release_conn_mgmt() to indicate the
923 * cmg needs to be unlocked or not.
924 */
925static ns_conn_mgmt_t *
926free_conn_mt(ns_conn_mt_t *cm, int unlock_cmg)
927{
928	ns_conn_mgmt_t	*cmg;
929
930	if (cm == NULL)
931		return (NULL);
932	if (cm->ns_error != NULL)
933		(void) __ns_ldap_freeError(&cm->ns_error);
934	if (cm->conn != NULL) {
935		if (cm->conn->ld != NULL)
936			(void) ldap_unbind(cm->conn->ld);
937		__s_api_freeConnection(cm->conn);
938	}
939	cmg = cm->conn_mgmt;
940	free(cm);
941	return (release_conn_mgmt(cmg, unlock_cmg));
942}
943
944/* add a connection user to an MT connection */
945static void
946add_cu2cm(ns_conn_user_t *cu, ns_conn_mt_t *cm)
947{
948
949	if (cm->cu_head == NULL) {
950		cm->cu_head = cu;
951		cm->cu_tail = cu;
952	} else {
953		cm->cu_tail->next = cu;
954		cm->cu_tail = cu;
955	}
956	cm->cu_cnt++;
957}
958
959/* add an MT connection to the connection management */
960static void
961add_cm2cmg(ns_conn_mt_t *cm, ns_conn_mgmt_t *cmg)
962{
963	/*
964	 * add connection opened for WRITE to top of list
965	 * for garbage collection purpose. This is to
966	 * ensure the connection will be closed after a
967	 * certain amount of time (60 seconds).
968	 */
969	if (cmg->cm_head == NULL) {
970		cmg->cm_head = cm;
971		cmg->cm_tail = cm;
972	} else {
973		if (cm->opened_for == NS_CONN_USER_WRITE) {
974			cm->next = cmg->cm_head;
975			cmg->cm_head = cm;
976		} else {
977			cmg->cm_tail->next = cm;
978			cmg->cm_tail = cm;
979		}
980	}
981	cmg->cm_cnt++;
982}
983
984/* delete a connection user from an MT connection */
985static void
986del_cu4cm(ns_conn_user_t *cu, ns_conn_mt_t *cm)
987{
988	ns_conn_user_t *pu, *u;
989
990	if (cu == NULL || cm->cu_head == NULL || cm->cu_cnt == 0)
991		return;
992
993	/* only one conn_user on list */
994	if (cm->cu_head == cm->cu_tail) {
995		if (cu == cm->cu_head) {
996			cm->cu_head = cm->cu_tail = NULL;
997			cm->cu_cnt = 0;
998			cu->next = NULL;
999		}
1000		return;
1001	}
1002
1003	/* more than one and cu is the first one */
1004	if (cu == cm->cu_head) {
1005		cm->cu_head = cu->next;
1006		cm->cu_cnt--;
1007		cu->next = NULL;
1008		return;
1009	}
1010
1011	pu = cm->cu_head;
1012	for (u = cm->cu_head->next; u; u = u->next) {
1013		if (cu == u)
1014			break;
1015		pu = u;
1016	}
1017	if (pu != cm->cu_tail) {
1018		pu->next = cu->next;
1019		if (pu->next == NULL)
1020			cm->cu_tail = pu;
1021		cm->cu_cnt--;
1022		cu->next = NULL;
1023	} else {
1024		syslog(LOG_INFO, gettext(
1025		    "libsldap: del_cu4cm(): connection user not found"));
1026	}
1027}
1028
1029/* delete an MT connection from the connection management control structure */
1030static void
1031del_cm4cmg(ns_conn_mt_t *cm, ns_conn_mgmt_t *cmg)
1032{
1033	ns_conn_mt_t *pm, *m;
1034
1035	if (cm == NULL || cmg->cm_head == NULL || cmg->cm_cnt == 0)
1036		return;
1037
1038	/* only one conn_mt on list */
1039	if (cmg->cm_head == cmg->cm_tail) {
1040		if (cm == cmg->cm_head) {
1041			cmg->cm_head = cmg->cm_tail = NULL;
1042			cmg->cm_cnt = 0;
1043			cm->next = NULL;
1044		}
1045		return;
1046	}
1047
1048	/* more than one and cm is the first one */
1049	if (cm == cmg->cm_head) {
1050		cmg->cm_head = cm->next;
1051		cmg->cm_cnt--;
1052		cm->next = NULL;
1053		return;
1054	}
1055
1056	pm = cmg->cm_head;
1057	for (m = cmg->cm_head->next; m; m = m->next) {
1058		if (cm == m)
1059			break;
1060		pm = m;
1061	}
1062	if (pm != cmg->cm_tail) {
1063		pm->next = cm->next;
1064		if (pm->next == NULL)
1065			cmg->cm_tail = pm;
1066		cmg->cm_cnt--;
1067		cm->next = NULL;
1068	} else {
1069		syslog(LOG_INFO, gettext(
1070		    "libsldap: del_cm4cmg(): MT connection not found"));
1071	}
1072}
1073
1074/*
1075 * compare to see if the server and credential for authentication match
1076 * those used by an MT connection
1077 */
1078static boolean_t
1079is_server_cred_matched(const char *server, const ns_cred_t *cred,
1080	ns_conn_mt_t *cm)
1081{
1082	Connection	*cp = cm->conn;
1083
1084	/* check server first */
1085	if (server != NULL && *server != 0) {
1086		if (strcasecmp(server, cp->serverAddr) != 0)
1087			return (B_FALSE);
1088	}
1089
1090	if (cred == NULL)
1091		return (B_TRUE);
1092
1093	/* then check cred */
1094	return (__s_api_is_auth_matched(cp->auth, cred));
1095}
1096
1097/*
1098 * Wait until a pending MT connection becomes available.
1099 * Return 1 if so, 0 if error.
1100 *
1101 * Assume the current conn_mgmt and the input conn_mt
1102 * are locked.
1103 */
1104static int
1105wait_for_conn_mt(ns_conn_user_t *cu, ns_conn_mt_t *cm)
1106{
1107
1108	cu->state = NS_CONN_USER_WAITING;
1109	add_cu2cm(cu, cm);
1110	cu->conn_mt = cm;
1111
1112	(void) mutex_unlock(&cm->lock);
1113	/*
1114	 * It could take some time so we don't want to hold
1115	 * cm->conn_mgmt across the wait
1116	 */
1117	(void) mutex_unlock(&(cm->conn_mgmt)->lock);
1118
1119	(void) mutex_lock(&cm->lock);
1120	/* check one more time see if need to wait */
1121	if (cm->state == NS_CONN_MT_CONNECTING) {
1122		(void) conn_wait(cm, cu);
1123
1124		/* cm->lock is locked again at this point */
1125
1126		cu->state = NS_CONN_USER_WOKEUP;
1127	}
1128
1129	if (cm->state == NS_CONN_MT_CONNECTED)
1130		return (1);
1131	else {
1132		del_cu4cm(cu, cm);
1133		cu->conn_mt = NULL;
1134		cu->bad_mt_conn = B_FALSE;
1135		return (0);
1136	}
1137}
1138
1139/*
1140 * Check and see if the input MT connection '*cm' should be closed.
1141 * In two cases, it should be closed. If a preferred server is
1142 * found to be up when ldap_cachemgr is queried and reported back.
1143 * Or when the server being used for the connection is found to
1144 * be down. Return B_FALSE if the connection is not closed (or not marked
1145 * to be closed), otherwise unlock mutex (*cm)->lock and return B_TRUE.
1146 * This function assumes conn_mgmt cmg and conn_mt *cm are locked.
1147 */
1148static boolean_t
1149check_and_close_conn(ns_conn_mgmt_t *cmg, ns_conn_mt_t **cm,
1150	ns_conn_user_t *cu) {
1151
1152	int rc;
1153	int j;
1154	int svridx = -1;
1155	int upidx = -1;
1156	int free_cm;
1157	ns_server_info_t sinfo;
1158	ns_ldap_error_t *errorp = NULL;
1159
1160	/*
1161	 * check only if preferred servers are defined
1162	 */
1163	if (cmg->pservers_loaded == B_FALSE)
1164		get_preferred_servers(B_FALSE, B_FALSE, cmg);
1165	if (cmg->pservers == NULL)
1166		return (B_FALSE);
1167
1168	/*
1169	 * ask ldap_cachemgr for the first available server
1170	 */
1171	rc = __s_api_requestServer(NS_CACHE_NEW, NULL,
1172	    &sinfo, &errorp, NS_CACHE_ADDR_IP);
1173	if (rc != NS_LDAP_SUCCESS || sinfo.server == NULL) {
1174		(void) __ns_ldap_freeError(&errorp);
1175		return (B_FALSE);
1176	}
1177
1178	/*
1179	 * Did ldap_cachemgr return a preferred server ?
1180	 */
1181	for (j = 0; cmg->pservers[j] != NULL; j++) {
1182		if (strcasecmp(sinfo.server, cmg->pservers[j]) != 0)
1183			continue;
1184		upidx = j;
1185		break;
1186	}
1187
1188	/*
1189	 * Is the server being used a preferred one ?
1190	 */
1191	for (j = 0; cmg->pservers[j] != NULL; j++) {
1192		if (strcasecmp(cmg->pservers[j], (*cm)->conn->serverAddr) != 0)
1193			continue;
1194		svridx = j;
1195		break;
1196	}
1197
1198	/*
1199	 * Need to fall back to a down-but-now-up preferred server ?
1200	 * A preferred server falls back to a more preferred one.
1201	 * A regular one falls back to any preferred ones. So if
1202	 * both are preferred ones and same index, or both
1203	 * are not preferred ones, then no need to close the
1204	 * connection.
1205	 */
1206	if ((upidx == -1 && svridx == -1) ||
1207	    (upidx != -1 && svridx != -1 && upidx == svridx)) {
1208		__s_api_free_server_info(&sinfo);
1209		return (B_FALSE);
1210	}
1211
1212	/*
1213	 * otherwise, 4 cases, all may need to close the connection:
1214	 * For case 1 and 2, both servers are preferred ones:
1215	 * 1. ldap_cachemgr returned a better one to use (upidx < svridx)
1216	 * 2. the server being used is down (upidx > svridx)
1217	 * 3. ldap_cachemgr returned a preferred one, but the server
1218	 *    being used is not, so need to fall back to the preferred server
1219	 * 4. ldap_cachemgr returned a non-preferred one, but the server
1220	 *    being used is a preferred one, so it must be down (since
1221	 *    ldap_cachemgr always returns a preferred one when possible).
1222	 * For case 1 & 3, close the READ connection when no user uses it.
1223	 * For 2 and 4, close the connection with error rc, LDAP_SERVER_DOWN.
1224	 */
1225	if (upidx != -1 && (svridx == -1 || upidx < svridx)) { /* case 1 & 3 */
1226		/* fallback does not make sense for WRITE/referred connection */
1227		if ((*cm)->opened_for == NS_CONN_USER_WRITE ||
1228		    (*cm)->referral == B_TRUE) {
1229			__s_api_free_server_info(&sinfo);
1230			return (B_FALSE);
1231		}
1232		free_cm = close_conn_mt_when_nouser(*cm);
1233		if (cmg->shutting_down == B_FALSE)
1234			cu->retry = B_TRUE;
1235	} else {
1236		ns_ldap_error_t *ep;
1237		ep = __s_api_make_error(LDAP_SERVER_DOWN,
1238		    NS_CONN_MSG_DOWN_FROM_CACHEMGR);
1239		/* cu has not been attached to cm yet, use NULL as cu pointer */
1240		free_cm = close_conn_mt(*cm, LDAP_SERVER_DOWN, &ep, NULL);
1241		if (cmg->shutting_down == B_FALSE)
1242			cu->retry = B_TRUE;
1243		(void) __ns_ldap_freeError(&ep);
1244	}
1245
1246	(void) mutex_unlock(&(*cm)->lock);
1247	if (free_cm == 1) {
1248		(void) free_conn_mt(*cm, 0);
1249		*cm = NULL;
1250	}
1251
1252	__s_api_free_server_info(&sinfo);
1253
1254	return (B_TRUE);
1255}
1256
1257/*
1258 * Check to see if a conn_mt matches the connection criteria from
1259 * a conn_user. Return B_TRUE if yes, B_FALSE, otherwise. The input
1260 * conn_mt pointer (*cmt) may be freed and *cmt will be set to NULL
1261 * to indicate so.
1262 * conn_mt *cmt and conn_mgmt cm->conn_mgmt are assumed locked.
1263 * cm->lock is unlocked at exit if rc is B_FALSE.
1264 */
1265static boolean_t
1266match_conn_mt(ns_conn_user_t *cu, ns_conn_mt_t **cmt,
1267	ns_conn_mt_state_t st, const char *server,
1268	const ns_cred_t *cred)
1269{
1270	boolean_t	matched = B_FALSE;
1271	boolean_t	drop_conn;
1272	int		free_cm = 0;
1273	ns_conn_mt_t	*cm = *cmt;
1274	ns_conn_mgmt_t	*cmg = cm->conn_mgmt;
1275
1276	if (cm->state != st || cm->close_when_nouser  == B_TRUE ||
1277	    cm->detached == B_TRUE || cm->pid != getpid() ||
1278	    cm->referral != cu->referral) {
1279		(void) mutex_unlock(&cm->lock);
1280		return (B_FALSE);
1281	}
1282
1283	/*
1284	 * if a conn_mt opened for WRITE is idle
1285	 * long enough, then close it. To improve
1286	 * the performance of applications, such
1287	 * as ldapaddent, a WRITE connection is
1288	 * given a short time to live in the
1289	 * connection pool, expecting the write
1290	 * requests to come in a quick succession.
1291	 * To save resource, the connection will
1292	 * be closed if idle more than 60 seconds.
1293	 */
1294	if (cm->opened_for == NS_CONN_USER_WRITE &&
1295	    cu->type != NS_CONN_USER_WRITE && cm->cu_cnt == 0 &&
1296	    ((time(NULL) - cm->access_time) > 60)) {
1297		/*
1298		 * NS_LDAP_INTERNAL is irrelevant here. There no
1299		 * conn_user to consume the rc
1300		 */
1301		free_cm = close_conn_mt(cm, NS_LDAP_INTERNAL, NULL, NULL);
1302		(void) mutex_unlock(&cm->lock);
1303		if (free_cm == 1) {
1304			(void) free_conn_mt(cm, 0);
1305			*cmt = NULL;
1306		}
1307		return (B_FALSE);
1308	}
1309
1310	switch (cu->type) {
1311	case NS_CONN_USER_SEARCH:
1312	case NS_CONN_USER_GETENT:
1313		if (cm->opened_for == NS_CONN_USER_SEARCH ||
1314		    cm->opened_for == NS_CONN_USER_GETENT)
1315			matched = B_TRUE;
1316		break;
1317
1318	case NS_CONN_USER_WRITE:
1319		if (cm->opened_for == NS_CONN_USER_WRITE)
1320			matched = B_TRUE;
1321		break;
1322
1323	default:
1324		matched = B_FALSE;
1325		break;
1326	}
1327
1328	if (matched == B_TRUE && ((server != NULL || cred != NULL) &&
1329	    is_server_cred_matched(server, cred, cm) == B_FALSE))
1330		matched = B_FALSE;
1331
1332	if (matched != B_FALSE) {
1333		/*
1334		 * Check and drop the 'connected' connection if
1335		 * necessary. Main nscd gets status changes from
1336		 * the ldap_cachemgr daemon directly via the
1337		 * GETSTATUSCHANGE door call, the standalone
1338		 * function works in a no ldap_cachemgr environment,
1339		 * so no need to check and drop connections.
1340		 */
1341		if (cm->state == NS_CONN_MT_CONNECTED &&
1342		    cmg->is_nscd == B_FALSE && !__s_api_isStandalone()) {
1343			drop_conn = check_and_close_conn(cmg, &cm, cu);
1344			if (drop_conn == B_TRUE) {
1345				if (cm == NULL)
1346					*cmt = NULL;
1347				return (B_FALSE);
1348			}
1349		}
1350
1351		/* check if max. users using or waiting for the connection */
1352		if ((cm->state == NS_CONN_MT_CONNECTED &&
1353		    cm->cu_max != NS_CONN_MT_USER_NO_MAX &&
1354		    cm->cu_cnt >= cm->cu_max) ||
1355		    (cm->state == NS_CONN_MT_CONNECTING &&
1356		    cm->cu_max != NS_CONN_MT_USER_NO_MAX &&
1357		    cm->waiter_cnt >= cm->cu_max - 1))
1358			matched = B_FALSE;
1359	}
1360
1361	if (matched == B_FALSE)
1362		(void) mutex_unlock(&cm->lock);
1363
1364	return (matched);
1365}
1366
1367/*
1368 * obtain an MT connection from the connection management for a conn_user
1369 *
1370 * Input:
1371 *   server	: server name or IP address
1372 *   flags	: libsldap API flags
1373 *   cred	: pointer to the user credential
1374 *   cu		: pointer to the conn_user structure
1375 * Output:
1376 *   session	: hold pointer to the Connection structure
1377 *   errorp	: hold pointer to error info (ns_ldap_error_t)
1378 */
1379int
1380__s_api_conn_mt_get(const char *server, const int flags, const ns_cred_t *cred,
1381	Connection **session, ns_ldap_error_t **errorp, ns_conn_user_t *cu)
1382{
1383	int		rc;
1384	int		i;
1385	ns_conn_mt_t	*cn;
1386	ns_conn_mt_state_t st;
1387	ns_conn_mgmt_t	*cmg;
1388
1389	if (errorp == NULL || cu == NULL || session == NULL)
1390		return (NS_LDAP_INVALID_PARAM);
1391
1392	*session = NULL;
1393	cmg = cu->conn_mgmt;
1394
1395	/*
1396	 * for pam_ldap, always try opening a new connection
1397	 */
1398	if (cu->type == NS_CONN_USER_AUTH)
1399		return (NS_LDAP_NOTFOUND);
1400
1401	/* if need a new conn, then don't reuse */
1402	if (flags & NS_LDAP_NEW_CONN)
1403		return (NS_LDAP_NOTFOUND);
1404
1405	if (flags & NS_LDAP_KEEP_CONN)
1406		cu->keep_conn = B_TRUE;
1407
1408	/*
1409	 * We want to use MT connection only if keep-connection flag is
1410	 * set or if MT was requested (or active)
1411	 */
1412	if (!((cmg->state == NS_CONN_MGMT_INACTIVE &&
1413	    cu->keep_conn == B_TRUE) || cmg->state == NS_CONN_MGMT_ACTIVE))
1414		return (NS_LDAP_NOTFOUND);
1415
1416	/* MT connection will be used now (if possible/available) */
1417	cu->use_mt_conn = B_TRUE;
1418
1419	NS_CONN_CHECK_ABORT_AND_LOCK(cmg, cu, errorp);
1420
1421	/* first look for a connection already open */
1422	st = NS_CONN_MT_CONNECTED;
1423	cu->state = NS_CONN_USER_FINDING;
1424	for (i = 0; i < 2; i++) {
1425		for (cn = cmg->cm_head; cn; cn = cn->next) {
1426			(void) mutex_lock(&cn->lock);
1427			rc = match_conn_mt(cu, &cn, st, server, cred);
1428			if (rc == B_FALSE && cn != NULL) /* not found */
1429				continue;
1430			if (cn == NULL) { /* not found and cn freed */
1431				/*
1432				 * as the conn_mt list could
1433				 * be different due to cn's
1434				 * deletion, scan the entire
1435				 * conn_mt list again
1436				 */
1437				st = NS_CONN_MT_CONNECTED;
1438				i = -1;
1439				break;
1440			}
1441
1442			/* return a connected one if found */
1443			if (cn->state == NS_CONN_MT_CONNECTED) {
1444				*session = cn->conn;
1445				add_cu2cm(cu, cn);
1446				cu->conn_mt = cn;
1447				cu->state = NS_CONN_USER_CONNECTED;
1448				(void) mutex_unlock(&cn->lock);
1449				(void) mutex_unlock(&cmg->lock);
1450				return (NS_LDAP_SUCCESS);
1451			}
1452
1453			/*
1454			 * if cn is not connecting, or allow only
1455			 * one user, skip it
1456			 */
1457			if (cn->state != NS_CONN_MT_CONNECTING ||
1458			    cn->cu_max == 1) {
1459				(void) mutex_unlock(&cn->lock);
1460				continue;
1461			}
1462
1463			/* wait for the connecting conn_mt */
1464			if (wait_for_conn_mt(cu, cn) != 1) {
1465				/*
1466				 * NS_LDAP_NOTFOUND signals that the function
1467				 * __s_api_check_libldap_MT_conn_support()
1468				 * detected that the lower libldap library
1469				 * does not support MT connection, so return
1470				 * NS_LDAP_NOTFOUND to let the caller to
1471				 * open a non-MT conneciton. Otherwise,
1472				 * connect error occurred, return
1473				 * NS_CONN_USER_CONNECT_ERROR
1474				 */
1475				if (cn->ns_rc != NS_LDAP_NOTFOUND)
1476					cu->state = NS_CONN_USER_CONNECT_ERROR;
1477				else {
1478					cu->state = NS_CONN_USER_FINDING;
1479					cu->use_mt_conn = B_FALSE;
1480				}
1481				(void) mutex_unlock(&cn->lock);
1482
1483				/* cmg->lock unlocked by wait_for_conn_mt() */
1484
1485				return (cn->ns_rc);
1486			}
1487
1488			/* return the newly available conn_mt */
1489			*session = cn->conn;
1490			cu->state = NS_CONN_USER_CONNECTED;
1491			(void) mutex_unlock(&cn->lock);
1492
1493			/* cmg->lock unlocked by wait_for_conn_mt() */
1494
1495			return (NS_LDAP_SUCCESS);
1496		}
1497
1498		/* next, look for a connecting conn_mt */
1499		if (i == 0)
1500			st = NS_CONN_MT_CONNECTING;
1501	}
1502
1503	/* no connection found, start opening one */
1504	cn = init_conn_mt(cmg, errorp);
1505	if (cn == NULL) {
1506		(void) mutex_unlock(&cmg->lock);
1507		return ((*errorp)->status);
1508	}
1509	cu->conn_mt = cn;
1510	cn->opened_for = cu->type;
1511	cn->referral = cu->referral;
1512	if (cmg->ldap_mt == B_TRUE)
1513		cn->cu_max = NS_CONN_MT_USER_MAX;
1514	else
1515		cn->cu_max = 1;
1516	add_cm2cmg(cn, cmg);
1517	(void) mutex_unlock(&cmg->lock);
1518
1519	return (NS_LDAP_NOTFOUND);
1520}
1521
1522
1523/*
1524 * add an MT connection to the connection management
1525 *
1526 * Input:
1527 *   con	: pointer to the Connection info
1528 *   cu		: pointer to the conn_user structure
1529 * Output:
1530 *   ep		: hold pointer to error info (ns_ldap_error_t)
1531 */
1532int
1533__s_api_conn_mt_add(Connection *con, ns_conn_user_t *cu, ns_ldap_error_t **ep)
1534{
1535	ns_conn_mgmt_t	*cmg = cu->conn_mgmt;
1536	ns_conn_mt_t	*cm = cu->conn_mt;
1537
1538	/* if the conn_mgmt is being shut down, return error */
1539	NS_CONN_CHECK_ABORT_AND_LOCK(cmg, cu, ep);
1540
1541	/*
1542	 * start the change monitor thread only if it
1543	 * hasn't been started and the process is the
1544	 * main nscd (not peruser nscd)
1545	 */
1546	if (cmg->procchg_started == B_FALSE && cmg->is_nscd == B_TRUE) {
1547		start_thread(cmg);
1548		cmg->procchg_started = B_TRUE;
1549	}
1550	(void) mutex_lock(&cm->lock);
1551	cm->conn = con;
1552	cm->state = NS_CONN_MT_CONNECTED;
1553	cm->pid = getpid();
1554	cm->create_time = time(NULL);
1555	cm->access_time = cm->create_time;
1556	cm->opened_for = cu->type;
1557	add_cu2cm(cu, cm);
1558	cu->conn_mt = cm;
1559	cu->state = NS_CONN_USER_CONNECTED;
1560	if (cmg->ldap_mt == B_TRUE)
1561		cm->cu_max = NS_CONN_MT_USER_MAX;
1562	else
1563		cm->cu_max = 1;
1564
1565	/* wake up the waiters if any */
1566	(void) conn_signal(cm);
1567
1568	(void) mutex_unlock(&cm->lock);
1569	(void) mutex_unlock(&cmg->lock);
1570
1571	return (NS_LDAP_SUCCESS);
1572}
1573
1574/*
1575 * return an MT connection to the pool when a conn user is done usint it
1576 *
1577 * Input:
1578 *   cu		: pointer to the conn_user structure
1579 * Output:	NONE
1580 */
1581void
1582__s_api_conn_mt_return(ns_conn_user_t *cu)
1583{
1584	ns_conn_mt_t	*cm;
1585	ns_conn_mgmt_t	*cmg;
1586
1587	if (cu == NULL || cu->use_mt_conn == B_FALSE)
1588		return;
1589	cm = cu->conn_mt;
1590	if (cm == NULL)
1591		return;
1592	cmg = cu->conn_mgmt;
1593
1594	(void) mutex_lock(&cm->lock);
1595	del_cu4cm(cu, cm);
1596	cu->state = NS_CONN_USER_DISCONNECTED;
1597	cu->conn_mt = NULL;
1598	cu->bad_mt_conn = B_FALSE;
1599
1600	/*
1601	 *  if this MT connection is no longer needed, or not usable, and
1602	 * no more conn_user uses it, then close it.
1603	 */
1604
1605	if ((cm->close_when_nouser == B_TRUE ||
1606	    cm->state != NS_CONN_MT_CONNECTED) && cm->cu_cnt == 0) {
1607		(void) mutex_unlock(&cm->lock);
1608		(void) mutex_lock(&cmg->lock);
1609		(void) mutex_lock(&cm->lock);
1610		del_cm4cmg(cm, cmg);
1611		/* use ns_conn_free (instead of 1) to avoid lint warning */
1612		NS_CONN_UNLOCK_AND_FREE(ns_conn_free, cm, cmg);
1613	} else {
1614		if (cm->state == NS_CONN_MT_CONNECTED && cm->cu_cnt == 0 &&
1615		    cm->conn != NULL && cm->conn->ld != NULL) {
1616			struct timeval	zerotime;
1617			LDAPMessage	*res;
1618
1619			zerotime.tv_sec = zerotime.tv_usec = 0L;
1620			/* clean up remaining results just in case */
1621			while (ldap_result(cm->conn->ld, LDAP_RES_ANY,
1622			    LDAP_MSG_ALL, &zerotime, &res) > 0) {
1623				if (res != NULL)
1624					(void) ldap_msgfree(res);
1625			}
1626		}
1627		(void) mutex_unlock(&cm->lock);
1628	}
1629}
1630
1631/* save error info (rc and ns_ldap_error_t) in the conn_mt */
1632static void
1633err2cm(ns_conn_mt_t *cm, int rc, ns_ldap_error_t **errorp) {
1634	ns_ldap_error_t	*ep;
1635
1636	cm->ns_rc = rc;
1637	cm->ns_error = NULL;
1638	if (errorp != NULL && *errorp != NULL) {
1639		ep = __s_api_copy_error(*errorp);
1640		if (ep == NULL)
1641			cm->ns_rc = NS_LDAP_MEMORY;
1642		else
1643			cm->ns_error = ep;
1644	}
1645}
1646
1647/* copy error info (rc and ns_ldap_error_t) from conn_mt to conn_user */
1648static void
1649err_from_cm(ns_conn_user_t *cu, ns_conn_mt_t *cm) {
1650	ns_ldap_error_t	*ep;
1651
1652	cu->ns_rc = cm->ns_rc;
1653	if (cu->ns_error != NULL)
1654		(void) __ns_ldap_freeError(&cu->ns_error);
1655	cu->ns_error = NULL;
1656	if (cm->ns_rc != NS_LDAP_SUCCESS && cm->ns_error != NULL) {
1657		ep = __s_api_copy_error(cm->ns_error);
1658		if (ep == NULL)
1659			cu->ns_rc = NS_LDAP_MEMORY;
1660		else
1661			cu->ns_error = ep;
1662	}
1663}
1664
1665/* copy error info (rc and ns_ldap_error_t) from caller to conn_user */
1666static void
1667err_from_caller(ns_conn_user_t *cu, int rc, ns_ldap_error_t **errorp) {
1668
1669	cu->ns_rc = rc;
1670	if (errorp != NULL) {
1671		if (cu->ns_error != NULL)
1672			(void) __ns_ldap_freeError(&cu->ns_error);
1673		cu->ns_error = *errorp;
1674		*errorp = NULL;
1675	} else
1676		cu->ns_error = NULL;
1677}
1678
1679/*
1680 * remove an MT connection from the connection management when failed to open
1681 *
1682 * Input:
1683 *   cu		: pointer to the conn_user structure
1684 *   rc		: error code
1685 *   errorp	: pointer to pointer to error info (ns_ldap_error_t)
1686 * Output:
1687 *   errorp	: set to NULL, if none NULL cm, callers do not need to free it
1688 */
1689void
1690__s_api_conn_mt_remove(ns_conn_user_t *cu, int rc, ns_ldap_error_t **errorp)
1691{
1692	ns_conn_mgmt_t	*cmg;
1693	ns_conn_mt_t	*cm;
1694	int		free_cm = 0;
1695
1696	if (cu == NULL || cu->use_mt_conn == B_FALSE)
1697		return;
1698	if ((cm = cu->conn_mt) == NULL)
1699		return;
1700	cmg = cu->conn_mgmt;
1701
1702	(void) mutex_lock(&cmg->lock);
1703	(void) mutex_lock(&cm->lock);
1704	if (cm->state != NS_CONN_MT_CONNECT_ERROR) {
1705		cm->state = NS_CONN_MT_CONNECT_ERROR;
1706		cm->ns_rc = rc;
1707		if (errorp != NULL) {
1708			cm->ns_error = *errorp;
1709			*errorp = NULL;
1710		}
1711	}
1712
1713	/* all the conn_users share the same error rc and ns_ldap_error_t */
1714	err_from_cm(cu, cm);
1715	/* wake up the waiters if any */
1716	(void) conn_signal(cm);
1717
1718	del_cu4cm(cu, cm);
1719	cu->conn_mt = NULL;
1720	cu->bad_mt_conn = B_FALSE;
1721	if (cm->cu_cnt == 0) {
1722		del_cm4cmg(cm, cmg);
1723		free_cm = 1;
1724	}
1725
1726	NS_CONN_UNLOCK_AND_FREE(free_cm, cm, cmg);
1727}
1728
1729/*
1730 * check to see if the underlying libldap supports multi-threaded client
1731 * (MT connections)
1732 */
1733int
1734__s_api_check_libldap_MT_conn_support(ns_conn_user_t *cu, LDAP *ld,
1735	ns_ldap_error_t **ep)
1736{
1737	int		rc;
1738	ns_conn_mgmt_t	*cmg;
1739
1740	/* if no need to check, just return success */
1741	if (cu->conn_mt == NULL || cu->use_mt_conn == B_FALSE)
1742		return (NS_LDAP_SUCCESS);
1743
1744	cmg = cu->conn_mgmt;
1745	rc = setup_mt_ld(ld, cmg);
1746
1747	if (cmg->do_mt_conn == B_FALSE) {
1748		/*
1749		 * If the conn_mgmt is being shut down, return error.
1750		 * if cmg is usable, cmg->lock will be locked. Otherwise,
1751		 * this function will return with rc NS_LDAP_OP_FAILED.
1752		 */
1753		NS_CONN_CHECK_ABORT_AND_LOCK(cmg, cu, ep);
1754		if (cmg->do_mt_conn == B_FALSE) {
1755			if (rc < 0)
1756				cmg->ldap_mt = B_FALSE;
1757			else {
1758				cmg->ldap_mt = B_TRUE;
1759				if (cmg->is_nscd  == B_TRUE ||
1760				    cmg->is_peruser_nscd == B_TRUE) {
1761					cmg->do_mt_conn = B_TRUE;
1762					cmg->state = NS_CONN_MGMT_ACTIVE;
1763				}
1764			}
1765		}
1766		(void) mutex_unlock(&cmg->lock);
1767	}
1768
1769	if (rc < 0)
1770		__s_api_conn_mt_remove(cu, NS_LDAP_NOTFOUND, NULL);
1771	return (NS_LDAP_SUCCESS);
1772}
1773
1774/*
1775 * Close an MT connection.
1776 * Assume cm not null and locked, assume conn_mgmt is also locked.
1777 * Return -1 if error, 1 if the cm should be freed, otherwise 0.
1778 */
1779static int
1780close_conn_mt(ns_conn_mt_t *cm, int rc, ns_ldap_error_t **errorp,
1781	ns_conn_user_t *cu)
1782{
1783	ns_conn_mgmt_t	*cmg = cm->conn_mgmt;
1784	ns_conn_mt_t	*m;
1785	ns_conn_user_t	*u;
1786
1787	if ((cm->state != NS_CONN_MT_CONNECTED && cm->state !=
1788	    NS_CONN_MT_CLOSING) || cmg->cm_head == NULL || cmg->cm_cnt == 0)
1789		return (-1);
1790
1791	/* if the conn_mt is not in the MT connection pool, nothing to do */
1792	for (m = cmg->cm_head; m; m = m->next) {
1793		if (cm == m)
1794			break;
1795	}
1796	if (m == NULL)
1797		return (-1);
1798
1799	if (cm->state == NS_CONN_MT_CONNECTED) { /* first time in here */
1800		cm->state = NS_CONN_MT_CLOSING;
1801		/*
1802		 * If more cu exist to consume the error info, copy
1803		 * it to the cm. If the caller calls on behalf of
1804		 * a cu, cu won't be NULL. Check to see if there's
1805		 * more cu that needs the error info. If caller does
1806		 * not have a specific cu attached to it (e.g.,
1807		 * shutdown_all_conn_mt()), cu is NULL, check if at
1808		 * least one cu exists.
1809		 */
1810		if ((cu != NULL && cm->cu_cnt > 1) ||
1811		    (cu == NULL && cm->cu_cnt > 0)) {
1812			err2cm(cm, rc, errorp);
1813			/* wake up waiter (conn_user) if any */
1814			(void) conn_signal(cm);
1815		}
1816
1817		/* for each conn_user using the conn_mt, set bad_mt_conn flag */
1818		if (cm->cu_head != NULL) {
1819			for (u = cm->cu_head; u; u = u->next) {
1820				u->bad_mt_conn = B_TRUE;
1821				if (cmg->shutting_down == B_FALSE)
1822					u->retry = B_TRUE;
1823			}
1824		}
1825	}
1826
1827	/* detach the conn_mt if no more conn_user left */
1828	if ((cu != NULL && cm->cu_cnt == 1) ||
1829	    (cu == NULL && cm->cu_cnt ==  0)) {
1830		del_cm4cmg(cm, cmg);
1831		cm->detached = B_TRUE;
1832		return (1);
1833	}
1834
1835	return (0);
1836}
1837
1838/*
1839 * An MT connection becomes bad, close it and free resources.
1840 * This function is called with a ns_conn_user_t representing
1841 * a user of the MT connection.
1842 *
1843 * Input:
1844 *   cu		: pointer to the conn_user structure
1845 *   rc		: error code
1846 *   errorp	: pointer to pointer to error info (ns_ldap_error_t)
1847 * Output:
1848 *   errorp	: set to NULL (if no error), callers do not need to free it
1849 */
1850void
1851__s_api_conn_mt_close(ns_conn_user_t *cu, int rc, ns_ldap_error_t **errorp)
1852{
1853	ns_conn_mgmt_t	*cmg;
1854	ns_conn_mt_t	*cm;
1855	int		free_cm = 0;
1856
1857	if (cu == NULL || cu->use_mt_conn == B_FALSE)
1858		return;
1859
1860	if (cu->state != NS_CONN_USER_CONNECTED || (cm = cu->conn_mt) == NULL)
1861		return;
1862	cmg = cu->conn_mgmt;
1863
1864	(void) mutex_lock(&cmg->lock);
1865	(void) mutex_lock(&cm->lock);
1866
1867	/* close the MT connection if possible */
1868	free_cm = close_conn_mt(cm, rc, errorp, cu);
1869	if (free_cm == -1) { /* error case */
1870		(void) mutex_unlock(&cm->lock);
1871		(void) mutex_unlock(&cmg->lock);
1872		return;
1873	}
1874
1875	if (rc != NS_LDAP_SUCCESS) { /* error info passed in, use it */
1876		err_from_caller(cu, rc, errorp);
1877	} else { /* error not passed in, use those saved in the conn_mt */
1878		err_from_cm(cu, cm);
1879	}
1880
1881	/* detach the conn_user from the conn_mt */
1882	del_cu4cm(cu, cm);
1883	cu->conn_mt = NULL;
1884	cu->bad_mt_conn = B_FALSE;
1885	if (cmg->shutting_down == B_FALSE)
1886		cu->retry = B_TRUE;
1887	NS_CONN_UNLOCK_AND_FREE(free_cm, cm, cmg);
1888}
1889
1890/*
1891 * Close an MT connection when the associated server is known to be
1892 * down. This function is called with a ns_conn_mt_t representing
1893 * the MT connection. That is, the caller is not a conn_user
1894 * thread but rather the procchg thread.
1895 */
1896static void
1897close_conn_mt_by_procchg(ns_conn_mt_t *cm, int rc, char *errmsg)
1898{
1899	ns_conn_mgmt_t	*cmg;
1900	int		free_cm = 0;
1901	ns_ldap_error_t	*ep;
1902
1903	if (cm == NULL)
1904		return;
1905	cmg = cm->conn_mgmt;
1906
1907	ep = (ns_ldap_error_t *)calloc(1, sizeof (*ep));
1908	if (ep != NULL) {
1909		ep->status = rc;
1910		if (errmsg != NULL)
1911			ep->message =  strdup(errmsg); /* OK if returns NULL */
1912	}
1913
1914	(void) mutex_lock(&cmg->lock);
1915	(void) mutex_lock(&cm->lock);
1916
1917	/* close the MT connection if possible */
1918	free_cm = close_conn_mt(cm, LDAP_SERVER_DOWN, &ep, NULL);
1919	if (free_cm == -1) { /* error case */
1920		(void) mutex_unlock(&cm->lock);
1921		(void) mutex_unlock(&cmg->lock);
1922		return;
1923	}
1924	(void) __ns_ldap_freeError(&ep);
1925
1926	NS_CONN_UNLOCK_AND_FREE(free_cm, cm, cmg);
1927}
1928
1929/*
1930 * Close an MT connection when there is a better server to connect to.
1931 * Mark the connection as to-be-closed-when-no-one-using so that
1932 * any outstanding ldap operations can run to completion.
1933 * Assume that both the conn_mt and conn_mgmt are locked.
1934 * Return 1 if the conn_mt should be freed.
1935 */
1936static int
1937close_conn_mt_when_nouser(ns_conn_mt_t *cm)
1938{
1939	int		free_cm = 0;
1940
1941	if (cm->cu_cnt == 0) {
1942		del_cm4cmg(cm, cm->conn_mgmt);
1943		free_cm = 1;
1944	} else {
1945		cm->close_when_nouser = B_TRUE;
1946	}
1947
1948	return (free_cm);
1949}
1950
1951/*
1952 * Retrieve the configured preferred server list.
1953 * This function locked the conn_mgmt and does not
1954 * unlock at exit.
1955 */
1956static void
1957get_preferred_servers(boolean_t lock, boolean_t reload, ns_conn_mgmt_t *cmg)
1958{
1959	ns_ldap_error_t *errorp = NULL;
1960	void		**pservers = NULL;
1961
1962	if (lock == B_TRUE)
1963		(void) mutex_lock(&cmg->lock);
1964
1965	/* if already done, and no reload, then return */
1966	if (cmg->pservers_loaded == B_TRUE && reload == B_FALSE)
1967		return;
1968
1969	if (cmg->pservers != NULL) {
1970		(void) __ns_ldap_freeParam((void ***)&cmg->pservers);
1971		cmg->pservers = NULL;
1972	}
1973
1974	if (__ns_ldap_getParam(NS_LDAP_SERVER_PREF_P,
1975	    &pservers, &errorp) == NS_LDAP_SUCCESS) {
1976		cmg->pservers = (char **)pservers;
1977		cmg->pservers_loaded = B_TRUE;
1978	} else {
1979		(void) __ns_ldap_freeError(&errorp);
1980		(void) __ns_ldap_freeParam(&pservers);
1981	}
1982}
1983
1984/*
1985 * This function handles the config or server status change notification
1986 * from the ldap_cachemgr.
1987 */
1988static ns_conn_mgmt_t *
1989proc_server_change(ns_server_status_change_t *chg, ns_conn_mgmt_t  *cmg)
1990{
1991	int		cnt, i, j, k, n;
1992	boolean_t	loop = B_TRUE;
1993	boolean_t	cmg_locked = B_FALSE;
1994	char 		*s;
1995	ns_conn_mt_t	*cm;
1996	ns_conn_mgmt_t	*ocmg;
1997
1998	/* if config changed, reload the configuration */
1999	if (chg->config_changed == B_TRUE) {
2000		/* reload the conn_mgmt and Native LDAP config */
2001		ocmg = access_conn_mgmt(NS_CONN_MGMT_OP_RELOAD_CONFIG);
2002		shutdown_all_conn_mt(ocmg);
2003		/* release the one obtained from access_conn_mgmt(RELOAD) */
2004		(void) release_conn_mgmt(ocmg, B_FALSE);
2005		/* release the one obtained when ocmg was created */
2006		(void) release_conn_mgmt(ocmg, B_FALSE);
2007		return (ocmg);
2008	}
2009
2010	if ((cnt = chg->num_server) == 0)
2011		return (cmg);
2012
2013	/* handle down servers first */
2014	for (i = 0; i < cnt; i++) {
2015
2016		if (chg->changes[i] != NS_SERVER_DOWN)
2017			continue;
2018		s = chg->servers[i];
2019
2020		/*
2021		 * look for a CONNECTED MT connection using
2022		 * the same server s, and close it
2023		 */
2024		while (loop) {
2025			if (cmg_locked == B_FALSE) {
2026				(void) mutex_lock(&cmg->lock);
2027				cmg_locked = B_TRUE;
2028			}
2029			for (cm = cmg->cm_head; cm; cm = cm->next) {
2030				(void) mutex_lock(&cm->lock);
2031
2032				if (cm->state == NS_CONN_MT_CONNECTED &&
2033				    cm->conn != NULL &&
2034				    strcasecmp(cm->conn->serverAddr, s) == 0) {
2035					(void) mutex_unlock(&cm->lock);
2036					break;
2037				}
2038
2039				(void) mutex_unlock(&cm->lock);
2040			}
2041			if (cm != NULL) {
2042				(void) mutex_unlock(&cmg->lock);
2043				cmg_locked = B_FALSE;
2044				close_conn_mt_by_procchg(cm, LDAP_SERVER_DOWN,
2045				    NS_CONN_MSG_DOWN_FROM_CACHEMGR);
2046				/*
2047				 * Process the next cm using server s.
2048				 * Start from the head of the cm linked
2049				 * list again, as the cm list may change
2050				 * after close_conn_mt_by_procchg() is done.
2051				 */
2052				continue;
2053			}
2054
2055			/*
2056			 * No (more) MT connection using the down server s.
2057			 * Process the next server on the list.
2058			 */
2059			break;
2060		} /* while loop */
2061	}
2062
2063	/*
2064	 * Next handle servers whose status changed to up.
2065	 * Get the preferred server list first if not done yet.
2066	 * get_preferred_servers() leaves conn_mgmt locked.
2067	 */
2068	get_preferred_servers(cmg_locked == B_FALSE ? B_TRUE : B_FALSE,
2069	    B_FALSE, cmg);
2070	cmg_locked = B_TRUE;
2071	/*
2072	 * if no preferred server configured, we don't switch MT connection
2073	 * to a more preferred server (i.e., fallback), so just return
2074	 */
2075	if (cmg->pservers == NULL) {
2076		(void) mutex_unlock(&cmg->lock);
2077		return (cmg);
2078	}
2079
2080	/* for each server that is up now */
2081	for (i = 0; i < cnt; i++) {
2082		if (chg->changes[i] != NS_SERVER_UP)
2083			continue;
2084		s = chg->servers[i];
2085
2086		/*
2087		 * look for a CONNECTED MT connection which uses
2088		 * a server less preferred than s, and treat it
2089		 * as 'fallback needed' by calling
2090		 * close_conn_mt_when_nouser()
2091		 */
2092		k = -1;
2093		loop = B_TRUE;
2094		while (loop) {
2095			if (cmg_locked == B_FALSE) {
2096				(void) mutex_lock(&cmg->lock);
2097				cmg_locked = B_TRUE;
2098			}
2099
2100			/* Is s a preferred server ? */
2101			if (k == -1) {
2102				for (j = 0; cmg->pservers[j] != NULL; j++) {
2103					if (strcasecmp(cmg->pservers[j],
2104					    s) == 0) {
2105						k = j;
2106						break;
2107					}
2108				}
2109			}
2110			/* skip s if not a preferred server */
2111			if (k == -1) {
2112				break;
2113			}
2114
2115			/* check each MT connection */
2116			for (cm = cmg->cm_head; cm; cm = cm->next) {
2117				(void) mutex_lock(&cm->lock);
2118				/*
2119				 * Find an MT connection that is connected and
2120				 * not marked, but leave WRITE or REFERRAL
2121				 * connections alone, since fallback does not
2122				 * make sense for them.
2123				 */
2124				if (cm->state == NS_CONN_MT_CONNECTED &&
2125				    cm->close_when_nouser == B_FALSE &&
2126				    cm->conn != NULL && cm->opened_for !=
2127				    NS_CONN_USER_WRITE &&
2128				    cm->referral == B_FALSE) {
2129					n = -1;
2130					/*
2131					 * j < k ??? should we close
2132					 * an active MT that is using s ?
2133					 * ie could s went down and up
2134					 * again, but cm is bound prior to
2135					 * the down ? Play safe here,
2136					 * and check j <= k.
2137					 */
2138					for (j = 0; j <= k; j++) {
2139						if (strcasecmp(
2140						    cm->conn->serverAddr,
2141						    cmg->pservers[j]) == 0) {
2142							n = j;
2143							break;
2144						}
2145					}
2146					/*
2147					 * s is preferred, if its location
2148					 * in the preferred server list is
2149					 * ahead of that of the server
2150					 * used by the cm (i.e., no match
2151					 * found before s)
2152					 */
2153					if (n == -1) { /* s is preferred */
2154						int fr = 0;
2155						fr = close_conn_mt_when_nouser(
2156						    cm);
2157						NS_CONN_UNLOCK_AND_FREE(fr,
2158						    cm, cmg);
2159						cmg_locked = B_FALSE;
2160						/*
2161						 * break, not continue,
2162						 * because we need to
2163						 * check the entire cm
2164						 * list again. The call
2165						 * above may change the
2166						 * cm list.
2167						 */
2168						break;
2169					}
2170				}
2171				(void) mutex_unlock(&cm->lock);
2172			}
2173			/* if no (more) cm using s, check next server */
2174			if (cm == NULL)
2175				loop = B_FALSE;
2176		} /* while loop */
2177	}
2178	if (cmg_locked == B_TRUE)
2179		(void) mutex_unlock(&cmg->lock);
2180	return (cmg);
2181}
2182
2183/* Shut down all MT connection managed by the connection management */
2184void
2185shutdown_all_conn_mt(ns_conn_mgmt_t  *cmg)
2186{
2187	ns_ldap_error_t	*ep;
2188	ns_conn_mt_t	*cm;
2189	int		free_cm = 0;
2190	boolean_t	done = B_FALSE;
2191
2192	ep = (ns_ldap_error_t *)calloc(1, sizeof (*ep));
2193	if (ep != NULL) { /* if NULL, not a problem */
2194		/* OK if returns NULL */
2195		ep->message = strdup(NS_CONN_MSG_SHUTDOWN_RELOADED);
2196	}
2197
2198	(void) mutex_lock(&cmg->lock);
2199	while (cmg->cm_head != NULL && done == B_FALSE) {
2200		for (cm = cmg->cm_head; cm; cm = cm->next) {
2201			(void) mutex_lock(&cm->lock);
2202			if (cm->next == NULL)
2203				done = B_TRUE;
2204			/* shut down each conn_mt, ignore errors */
2205			free_cm = close_conn_mt(cm, LDAP_OTHER, &ep, NULL);
2206			(void) mutex_unlock(&cm->lock);
2207			if (free_cm == 1) {
2208				(void) free_conn_mt(cm, 0);
2209				/*
2210				 * conn_mt may change, so start from
2211				 * top of list again
2212				 */
2213				break;
2214			}
2215		}
2216	}
2217	(void) mutex_unlock(&cmg->lock);
2218	(void) __ns_ldap_freeError(&ep);
2219}
2220
2221/* free all the resources used by the connection management */
2222void
2223__s_api_shutdown_conn_mgmt()
2224{
2225	ns_conn_mgmt_t	*cmg;
2226
2227	cmg = access_conn_mgmt(NS_CONN_MGMT_OP_SHUTDOWN);
2228	if (cmg == NULL) /* already being SHUT done */
2229		return;
2230
2231	(void) shutdown_all_conn_mt(cmg);
2232	(void) release_conn_mgmt(cmg, B_FALSE);
2233
2234	/* then destroy the conn_mgmt */
2235	(void) release_conn_mgmt(cmg, B_FALSE);
2236}
2237
2238
2239/*
2240 * reinitialize the libsldap connection management after
2241 * receiving a new native LDAP configuration from ldap_cachemgr
2242 */
2243void
2244__s_api_reinit_conn_mgmt_new_config(ns_config_t *new_cfg)
2245{
2246	ns_conn_mgmt_t	*cmg;
2247	ns_conn_mgmt_t	*ocmg;
2248
2249	cmg = access_conn_mgmt(NS_CONN_MGMT_OP_REF);
2250	if (cmg == NULL)
2251		return;
2252	if (cmg->config == new_cfg || cmg->state == NS_CONN_MGMT_DETACHED) {
2253		(void) release_conn_mgmt(cmg, B_FALSE);
2254		return;
2255	}
2256
2257	/* reload the conn_mgmt and native LDAP config */
2258	ocmg = access_conn_mgmt(NS_CONN_MGMT_OP_NEW_CONFIG);
2259	if (ocmg == cmg)
2260		shutdown_all_conn_mt(ocmg);
2261	/* release the one obtained from access_conn_mgmt(RELOAD) */
2262	(void) release_conn_mgmt(ocmg, B_FALSE);
2263	/* release the one obtained when ocmg was created */
2264	(void) release_conn_mgmt(ocmg, B_FALSE);
2265	/* release the one obtained when this function is entered */
2266	(void) release_conn_mgmt(cmg, B_FALSE);
2267}
2268
2269/*
2270 * Prepare to retry ldap search operation if needed.
2271 * Return 1 if retry is needed, otherwise 0.
2272 * If first time in, return 1. If not, return 1 if:
2273 * - not a NS_CONN_USER_GETENT conn_user AND
2274 * - have not retried 3 times yet AND
2275 * - previous search failed AND
2276 * - the retry flag is set in the ns_conn_user_t or config was reloaded
2277 */
2278int
2279__s_api_setup_retry_search(ns_conn_user_t **conn_user,
2280	ns_conn_user_type_t type, int *try_cnt, int *rc,
2281	ns_ldap_error_t **errorp)
2282{
2283	boolean_t	retry;
2284	ns_conn_user_t	*cu = *conn_user;
2285	ns_conn_mgmt_t	*cmg;
2286
2287	if (*try_cnt > 0 && cu != NULL) {
2288		/*
2289		 * if called from firstEntry(), keep conn_mt for
2290		 * the subsequent getnext requests
2291		 */
2292		if (cu->type == NS_CONN_USER_GETENT && *rc == NS_LDAP_SUCCESS)
2293			return (0);
2294		cmg = cu->conn_mgmt;
2295		retry = cu->retry;
2296		if (cu->conn_mt != NULL)
2297			__s_api_conn_mt_return(cu);
2298		if (cmg != NULL && cmg->cfg_reloaded == B_TRUE)
2299			retry = B_TRUE;
2300		__s_api_conn_user_free(cu);
2301		*conn_user = NULL;
2302
2303		if (*rc == NS_LDAP_SUCCESS || retry != B_TRUE)
2304			return (0);
2305	}
2306
2307	*try_cnt = *try_cnt + 1;
2308	if (*try_cnt > NS_LIST_TRY_MAX)
2309		return (0);
2310
2311	*conn_user = __s_api_conn_user_init(type, NULL, B_FALSE);
2312	if (*conn_user == NULL) {
2313		if (*try_cnt == 1) { /* first call before any retry */
2314			*rc = NS_LDAP_MEMORY;
2315			*errorp = NULL;
2316		}
2317		/* for 1+ try, use previous rc and errorp */
2318		return (0);
2319	}
2320
2321	/* free ldap_error_t from previous search */
2322	if (*try_cnt > 1 && rc != NS_LDAP_SUCCESS && *errorp != NULL)
2323		(void) __ns_ldap_freeError(errorp);
2324
2325	return (1);
2326}
2327
2328/* prepare to get the next entry for an enumeration */
2329int
2330__s_api_setup_getnext(ns_conn_user_t *cu, int *ns_err,
2331	ns_ldap_error_t **errorp)
2332{
2333	int rc;
2334	ns_conn_mgmt_t	*cmg;
2335
2336	/*
2337	 * if using an MT connection, ensure the thread-specific data are set,
2338	 * but if the MT connection is no longer good, return the error saved.
2339	 */
2340	if (cu->conn_mt != NULL && (cmg = cu->conn_mgmt) != NULL) {
2341
2342		if (cu->bad_mt_conn ==  B_TRUE) {
2343			__s_api_conn_mt_close(cu, 0, NULL);
2344			*ns_err = cu->ns_rc;
2345			*errorp = cu->ns_error;
2346			cu->ns_error = NULL;
2347			return (*ns_err);
2348		}
2349
2350		rc = conn_tsd_check(cmg);
2351		if (rc != NS_LDAP_SUCCESS) {
2352			*errorp = NULL;
2353			return (rc);
2354		}
2355	}
2356
2357	return (NS_LDAP_SUCCESS);
2358}
2359
2360/* wait for an MT connection to become available */
2361static int
2362conn_wait(ns_conn_mt_t *conn_mt, ns_conn_user_t *conn_user)
2363{
2364	ns_conn_waiter_t	mywait;
2365	ns_conn_waiter_t	*head = &conn_mt->waiter;
2366
2367	(void) cond_init(&(mywait.waitcv), USYNC_THREAD, 0);
2368	mywait.key = conn_user;
2369	mywait.signaled = 0;
2370	mywait.next = head->next;
2371	mywait.prev = head;
2372	if (mywait.next)
2373		mywait.next->prev = &mywait;
2374	head->next = &mywait;
2375	atomic_inc_uint(&conn_mt->waiter_cnt);
2376
2377	while (!mywait.signaled)
2378		(void) cond_wait(&(mywait.waitcv), &conn_mt->lock);
2379	if (mywait.prev)
2380		mywait.prev->next = mywait.next;
2381	if (mywait.next)
2382		mywait.next->prev = mywait.prev;
2383	return (0);
2384}
2385
2386/* signal that an MT connection is now available */
2387static int
2388conn_signal(ns_conn_mt_t *conn_mt)
2389{
2390	int			c = 0;
2391	ns_conn_waiter_t	*head = &conn_mt->waiter;
2392	ns_conn_waiter_t	*tmp = head->next;
2393
2394	while (tmp) {
2395		(void) cond_signal(&(tmp->waitcv));
2396		tmp->signaled = 1;
2397		atomic_dec_uint(&conn_mt->waiter_cnt);
2398		c++;
2399		tmp = tmp->next;
2400	}
2401
2402	return (c);
2403}
2404
2405/*
2406 * wait and process the server status and/or config change notification
2407 * from ldap_cachemgr
2408 */
2409static void *
2410get_server_change(void *arg)
2411{
2412	union {
2413		ldap_data_t	s_d;
2414		char		s_b[DOORBUFFERSIZE];
2415	} space;
2416	ldap_data_t	*sptr = &space.s_d;
2417	int		ndata;
2418	int		adata;
2419	char		*ptr;
2420	int		ds_cnt;
2421	int		door_rc;
2422	int		which;
2423	int		retry = 0;
2424	boolean_t	loop = B_TRUE;
2425	char		*c, *oc;
2426	int		dslen = strlen(DOORLINESEP);
2427	char		dsep = DOORLINESEP_CHR;
2428	char		chg_data[DOORBUFFERSIZE];
2429	char		**servers = NULL;
2430	boolean_t	getchg_not_supported = B_FALSE;
2431	ns_conn_mgmt_t	*ocmg = (ns_conn_mgmt_t *)arg;
2432	ns_conn_mgmt_t	*cmg;
2433	ns_server_status_t *status = NULL;
2434	ns_server_status_change_t chg = { 0 };
2435	ldap_get_change_out_t *get_chg;
2436	ldap_get_chg_cookie_t cookie;
2437	ldap_get_chg_cookie_t new_cookie;
2438
2439	cmg = access_conn_mgmt(NS_CONN_MGMT_OP_REF);
2440	if (cmg != ocmg)
2441		thr_exit(NULL);
2442	/* cmg is locked before called */
2443	cmg->procchg_tid = thr_self();
2444
2445	/* make sure the thread specific data are set */
2446	(void) conn_tsd_setup(cmg);
2447	cookie = cmg->cfg_cookie;
2448
2449	while (loop) {
2450
2451		if (chg.servers != NULL)
2452			free(chg.servers);
2453		if (chg.changes != NULL)
2454			free(chg.changes);
2455		if (sptr != &space.s_d)
2456			(void) munmap((char *)sptr, sizeof (space));
2457
2458		/*
2459		 * If the attached conn_mgmt has been deleted,
2460		 * then exit. The new conn_mgmt will starts it
2461		 * own monitor thread later. If libsldap is being
2462		 * unloaded or configuration reloaded, OR
2463		 * ldap_cachemgr rejected the GETSTATUSCHANGE door
2464		 * call, then exit as well.
2465		 */
2466		if (cmg == NULL || cmg->state == NS_CONN_MGMT_DETACHED ||
2467		    getchg_not_supported == B_TRUE) {
2468
2469			if (cmg != NULL) {
2470				cmg->procchg_started = B_FALSE;
2471				(void) release_conn_mgmt(cmg, B_FALSE);
2472			}
2473
2474			conn_tsd_free();
2475			thr_exit(NULL);
2476		}
2477
2478		(void) memset(space.s_b, 0, DOORBUFFERSIZE);
2479		(void) memset(&chg, 0, sizeof (chg));
2480		adata = sizeof (ldap_call_t) + 1;
2481		ndata = sizeof (space);
2482		space.s_d.ldap_call.ldap_callnumber = GETSTATUSCHANGE;
2483		space.s_d.ldap_call.ldap_u.get_change.op =
2484		    NS_STATUS_CHANGE_OP_START;
2485		space.s_d.ldap_call.ldap_u.get_change.cookie = cookie;
2486		sptr = &space.s_d;
2487		door_rc = __ns_ldap_trydoorcall_getfd();
2488		cmg->procchg_door_call = B_TRUE;
2489		if (release_conn_mgmt(cmg, B_FALSE) == NULL) {
2490			conn_tsd_free();
2491			thr_exit(NULL);
2492		}
2493
2494		if (door_rc == NS_CACHE_SUCCESS)
2495			door_rc = __ns_ldap_trydoorcall_send(&sptr, &ndata,
2496			    &adata);
2497
2498		/*
2499		 * Check and see if the conn_mgmt is still current.
2500		 * If not, no need to continue.
2501		 */
2502		cmg = access_conn_mgmt(NS_CONN_MGMT_OP_REF);
2503		if (cmg != NULL)
2504			cmg->procchg_door_call = B_FALSE;
2505		if (cmg != ocmg) {
2506			if (cmg != NULL) {
2507				cmg->procchg_started = B_FALSE;
2508				(void) release_conn_mgmt(cmg, B_FALSE);
2509			}
2510			conn_tsd_free();
2511			thr_exit(NULL);
2512		}
2513
2514		if (door_rc != NS_CACHE_SUCCESS) {
2515			if (door_rc == NS_CACHE_NOSERVER) {
2516				if (retry++ > 10)
2517					getchg_not_supported = B_TRUE;
2518				else {
2519					/*
2520					 * ldap_cachemgr may be down, give
2521					 * it time to restart
2522					 */
2523					(void) sleep(2);
2524				}
2525			} else if (door_rc == NS_CACHE_NOTFOUND)
2526				getchg_not_supported = B_TRUE;
2527			continue;
2528		} else
2529			retry = 0;
2530
2531		/* copy info from door call return structure */
2532		get_chg =  &sptr->ldap_ret.ldap_u.changes;
2533		ptr = get_chg->data;
2534		/* configuration change ? */
2535		if (get_chg->type == NS_STATUS_CHANGE_TYPE_CONFIG) {
2536			chg.config_changed = B_TRUE;
2537			cmg = proc_server_change(&chg, cmg);
2538			continue;
2539		}
2540
2541		/* server status changes ? */
2542		if (get_chg->type == NS_STATUS_CHANGE_TYPE_SERVER) {
2543			/*
2544			 * first check cookies, if don't match, config
2545			 * has changed
2546			 */
2547			new_cookie = get_chg->cookie;
2548			if (new_cookie.mgr_pid != cookie.mgr_pid ||
2549			    new_cookie.seq_num != cookie.seq_num) {
2550				chg.config_changed = B_TRUE;
2551				cmg = proc_server_change(&chg, cmg);
2552				continue;
2553			}
2554
2555			(void) strlcpy(chg_data, ptr, sizeof (chg_data));
2556			chg.num_server = get_chg->server_count;
2557
2558			servers = (char **)calloc(chg.num_server,
2559			    sizeof (char *));
2560			if (servers == NULL) {
2561				syslog(LOG_INFO, NS_CONN_MSG_MEMORY_ERROR);
2562				continue;
2563			}
2564			status = (ns_server_status_t *)calloc(chg.num_server,
2565			    sizeof (int));
2566			if (status == NULL) {
2567				syslog(LOG_INFO, NS_CONN_MSG_MEMORY_ERROR);
2568				free(servers);
2569				continue;
2570			}
2571			ds_cnt = 0;
2572			which = 0;
2573			oc = ptr;
2574			for (c = ptr; which != 2; c++) {
2575				/* look for DOORLINESEP or end of string */
2576				if (*c != dsep && *c != '\0')
2577					continue;
2578				if (*c == dsep) { /* DOORLINESEP */
2579					*c = '\0'; /* current value */
2580					c += dslen; /* skip to next value */
2581				}
2582				if (which == 0) { /* get server info */
2583					servers[ds_cnt] = oc;
2584					oc = c;
2585					which = 1; /* get status next */
2586					continue;
2587				}
2588				/* which == 1, get up/down status */
2589				if (strcmp(NS_SERVER_CHANGE_UP, oc) == 0) {
2590					status[ds_cnt] = NS_SERVER_UP;
2591				} else if (strcmp(NS_SERVER_CHANGE_DOWN,
2592				    oc) == 0)
2593					status[ds_cnt] = NS_SERVER_DOWN;
2594				else {
2595					syslog(LOG_INFO,
2596					    NS_CONN_MSG_BAD_CACHEMGR_DATA);
2597					continue;
2598				}
2599				oc = c;
2600				ds_cnt++;
2601				if (*c == '\0')
2602					which = 2; /* exit the loop */
2603				else
2604					which = 0; /* get server info next */
2605			}
2606			chg.servers = servers;
2607			chg.changes = status;
2608			cmg = proc_server_change(&chg, cmg);
2609			continue;
2610		}
2611	}
2612
2613	return (NULL);
2614}
2615
2616/* start the thread handling the change notification from ldap_cachemgr */
2617static void
2618start_thread(ns_conn_mgmt_t *cmg) {
2619
2620	int		errnum;
2621
2622	/*
2623	 * start a thread to get and process config and server status changes
2624	 */
2625	if (thr_create(NULL, NULL, get_server_change,
2626	    (void *)cmg, THR_DETACHED, NULL) != 0) {
2627		errnum = errno;
2628		syslog(LOG_WARNING, NS_CONN_MSG_NO_PROCCHG_THREAD,
2629		    strerror(errnum));
2630	}
2631}
2632