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