xref: /illumos-gate/usr/src/cmd/ldapcachemgr/cachemgr_change.c (revision c6fc6ddd482fa5c17788a38bd238258f2c22597c)
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 <strings.h>
29 #include <stdlib.h>
30 #include <syslog.h>
31 #include <errno.h>
32 #include <libintl.h>
33 #include <door.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <procfs.h>
38 #include "cachemgr.h"
39 
40 extern admin_t	current_admin;
41 
42 #define	CLEANUP_WAIT_TIME 60
43 
44 typedef enum cleanup_type {
45 	CLEANUP_ALL	= 1,
46 	CLEANUP_BY_PID	= 2
47 } cleanup_type_t;
48 
49 typedef struct cleanup_op {
50 	pid_t		pid;
51 	cleanup_type_t	type;
52 } cleanup_op_t;
53 
54 typedef struct main_nscd_struct {
55 	pid_t		pid;			/* main nscd pid */
56 	thread_t	tid;			/* main nscd tid */
57 	int		in_progress;		/* A main nscd thread is */
58 						/* waiting for change or */
59 						/* copying data */
60 	int		is_waiting_cleanup;	/* A main nscd thread is */
61 						/* waiting for another main */
62 						/* nscd thread to be cleaned */
63 						/* up */
64 } main_nscd_t;
65 
66 static chg_info_t chg = { DEFAULTMUTEX, DEFAULTCV, 0, NULL, NULL, NULL, 0 };
67 
68 static main_nscd_t chg_main_nscd = {0, 0, 0, 0};
69 static mutex_t chg_main_nscd_lock = DEFAULTMUTEX;
70 static cond_t chg_main_nscd_cv = DEFAULTCV;
71 
72 /*
73  * The cookie of the configuration and its mutex
74  */
75 static ldap_get_chg_cookie_t config_cookie = {0, 0};
76 static mutex_t config_cookie_lock = DEFAULTMUTEX;
77 
78 static void cleanup_thread_by_pid(pid_t pid);
79 
80 ldap_get_chg_cookie_t
81 chg_config_cookie_get(void)
82 {
83 	ldap_get_chg_cookie_t cookie;
84 	(void) mutex_lock(&config_cookie_lock);
85 	cookie = config_cookie;
86 	(void) mutex_unlock(&config_cookie_lock);
87 	return (cookie);
88 }
89 
90 static void
91 chg_config_cookie_increment_seq_num(void)
92 {
93 	(void) mutex_lock(&config_cookie_lock);
94 	config_cookie.seq_num++;
95 	(void) mutex_unlock(&config_cookie_lock);
96 }
97 
98 void
99 chg_config_cookie_set(ldap_get_chg_cookie_t *cookie)
100 {
101 	(void) mutex_lock(&config_cookie_lock);
102 	config_cookie.mgr_pid = cookie->mgr_pid;
103 	config_cookie.seq_num = cookie->seq_num;
104 	(void) mutex_unlock(&config_cookie_lock);
105 }
106 static boolean_t
107 chg_cookie_equal(ldap_get_chg_cookie_t *c1, ldap_get_chg_cookie_t *c2)
108 {
109 	if (c1->mgr_pid == c2->mgr_pid && c1->seq_num == c2->seq_num)
110 		return (B_TRUE);
111 	else
112 		return (B_FALSE);
113 }
114 /*
115  * Create a node in the list and output the node. The caller can NOT free it.
116  */
117 static  int
118 waiting_list_add(chg_info_t *info, pid_t pid, thread_t tid,
119     waiting_list_t **wlp)
120 {
121 
122 	waiting_list_t	*wl;
123 
124 	*wlp = NULL;
125 
126 	if ((wl = (waiting_list_t *)calloc(1, sizeof (waiting_list_t)))
127 	    == NULL) {
128 		logit("waiting_list_add: No memory. pid %ld tid %d\n",
129 		    pid, tid);
130 		return (CHG_NO_MEMORY);
131 	}
132 
133 	wl->pid = pid;
134 	wl->tid = tid;
135 
136 	if (info->chg_w_first == NULL) {
137 		info->chg_w_first = wl;
138 		info->chg_w_last = wl;
139 	} else {
140 		info->chg_w_last->next = wl;
141 		wl->prev = info->chg_w_last;
142 		info->chg_w_last = wl;
143 	}
144 	*wlp = wl;
145 	return (CHG_SUCCESS);
146 }
147 
148 /*
149  * Find a node with matching tid in the list and remove it from the list.
150  */
151 static int
152 waiting_list_delete(chg_info_t *info, thread_t tid)
153 {
154 	waiting_list_t	*wl;
155 
156 	for (wl = info->chg_w_first; wl != NULL; wl = wl->next) {
157 		if (wl->tid == tid) {
158 			if (wl->next == NULL) {
159 				if (wl->prev == NULL) {
160 					info->chg_w_first = NULL;
161 					info->chg_w_last = NULL;
162 				} else {
163 					wl->prev->next = NULL;
164 					info->chg_w_last =  wl->prev;
165 				}
166 			} else {
167 				if (wl->prev == NULL) {
168 					wl->next->prev = NULL;
169 					info->chg_w_first = wl->next;
170 				} else {
171 					wl->prev->next = wl->next;
172 					wl->next->prev = wl->prev;
173 				}
174 			}
175 			free(wl);
176 			return (CHG_SUCCESS);
177 		}
178 	}
179 	return (CHG_NOT_FOUND_IN_WAITING_LIST);
180 }
181 
182 /*
183  * Delete the thread from the waiting list and remove data when the list
184  * is empty.
185  */
186 static void
187 waiting_list_cleanup(chg_info_t *chg, thread_t tid)
188 {
189 	int	rc;
190 
191 	rc = waiting_list_delete(chg, tid);
192 
193 	if (rc == CHG_SUCCESS && chg->chg_w_first == NULL) {
194 		free(chg->chg_data);
195 		chg->chg_data = NULL;
196 		chg->chg_wakeup = 0;
197 	}
198 }
199 
200 /*
201  * Set flag by pid so it can be cleaned up.
202  */
203 static void
204 waiting_list_set_cleanup(chg_info_t *info, pid_t pid)
205 {
206 	waiting_list_t	*wl;
207 
208 	for (wl = info->chg_w_first; wl != NULL; wl = wl->next) {
209 		if (wl->pid == pid) {
210 			wl->cleanup = 1;
211 			break;
212 		}
213 	}
214 }
215 
216 /*
217  * Return: 1 - door client is dead, 0 - door client is alive
218  */
219 static int
220 door_client_dead(void)
221 {
222 	ucred_t *uc = NULL;
223 	int	rc;
224 
225 	if (door_ucred(&uc) == -1 && errno == EINVAL) {
226 		rc = 1;
227 	} else {
228 		rc = 0;
229 	}
230 	if (uc)
231 		ucred_free(uc);
232 
233 	return (rc);
234 }
235 
236 /*
237  * This function handles GETSTATUSCHANGE call from main nscd.
238  * The call can be a START op or STOP op. A cookie is sent from main nscd too.
239  * The static global variable main_nscd keeps record of pid, tid and some flags.
240  * If the thread is door_return(), main_nscd.pid, main_nscd.tid are set to 0.
241  * When the call is START op, it checks if main_nscd.pid is 0. If it is, it
242  * proceeds to wait for the change notification. If it's not, which means
243  * another main nscd handling thread is still around. It sends broadcast to
244  * clean up that thread and wait until the cleanup is done then proceeds to
245  * wait for the change notification. If same main nscd sends START op
246  * repeatedly, it'll be rejected.
247  * It also checks the cookie from main nscd. If it's not the same as
248  * ldap_cachemgr's cookie, door returns config change.
249  * If the door call is STOP op, it creates a thread to clean up main nscd START
250  * thread so it won't be blocking.
251  * In waiting for the change notification phase, the thread is waken up by
252  * the notification threads or by the cleanup threads.
253  * If it's a notification, it copies data to the stack then door return.
254  * If it's a cleanup, door_client_dead() is called to verify it then
255  * door return.
256  */
257 int
258 chg_get_statusChange(LineBuf *info, ldap_call_t *in, pid_t nscd_pid)
259 {
260 	int	rc = CHG_SUCCESS, another_main_nscd_thread_alive = 0;
261 	int	len, return_now;
262 	thread_t this_tid = thr_self();
263 	waiting_list_t	*wl = NULL;
264 	ldap_get_change_out_t *cout;
265 	ldap_get_chg_cookie_t cookie;
266 
267 	info->str = NULL;
268 	info->len = 0;
269 
270 	if (in->ldap_u.get_change.op == NS_STATUS_CHANGE_OP_START) {
271 
272 		(void) mutex_lock(&chg_main_nscd_lock);
273 		if (chg_main_nscd.pid != 0) {
274 			if (nscd_pid != chg_main_nscd.pid) {
275 				/*
276 				 * This is the case that nscd doesn't shut down
277 				 * properly(e.g. core) and STOP op is not sent,
278 				 * the thread handling it is still around and
279 				 * not cleaned up yet.
280 				 * Test if the thread is still alive.
281 				 * If it is, clean it up.
282 				 * For thr_kill, if sig is 0, a validity check
283 				 * is done for the existence of the target
284 				 * thread; no signal is sent.
285 				 */
286 				if (thr_kill(chg_main_nscd.tid, 0) == 0) {
287 					another_main_nscd_thread_alive = 1;
288 					cleanup_thread_by_pid(
289 					    chg_main_nscd.pid);
290 				}
291 			} else if (chg_main_nscd.in_progress ||
292 			    chg_main_nscd.is_waiting_cleanup) {
293 				/*
294 				 * Same nscd pid can only send door call
295 				 * one at a time and wait for ldap_cachemgr to
296 				 * return change data. If it's the same pid
297 				 * again, it's an nscd error.
298 				 */
299 				(void) mutex_unlock(&chg_main_nscd_lock);
300 				return (CHG_NSCD_REPEATED_CALL);
301 			}
302 		}
303 		/*
304 		 * Wait for another thread to be cleaned up if it's alive.
305 		 * After that this cond var is waken up.
306 		 */
307 		if (another_main_nscd_thread_alive) {
308 			while (chg_main_nscd.in_progress) {
309 				chg_main_nscd.is_waiting_cleanup = 1;
310 				(void) cond_wait(&chg_main_nscd_cv,
311 				    &chg_main_nscd_lock);
312 			}
313 		}
314 
315 		/*
316 		 * Replace pid and tid and set the flag.
317 		 */
318 		chg_main_nscd.is_waiting_cleanup = 0;
319 		chg_main_nscd.pid = nscd_pid;
320 		chg_main_nscd.tid = this_tid;
321 		chg_main_nscd.in_progress = 1;
322 		(void) mutex_unlock(&chg_main_nscd_lock);
323 
324 		cookie = chg_config_cookie_get();
325 
326 		if (!chg_cookie_equal(&cookie, &in->ldap_u.get_change.cookie)) {
327 			/*
328 			 * different cookie, set new cookie and
329 			 * return door call right away
330 			 */
331 			len = sizeof (ldap_get_change_out_t);
332 			if ((cout = calloc(1, len)) == NULL) {
333 				rc = CHG_NO_MEMORY;
334 			} else {
335 				cout->type = NS_STATUS_CHANGE_TYPE_CONFIG;
336 				cout->cookie = cookie;
337 				info->str = (char *)cout;
338 				info->len = len;
339 			}
340 
341 		} else {
342 			(void) mutex_lock(&chg.chg_lock);
343 
344 			/* wait for the change notification */
345 			rc = waiting_list_add(&chg, nscd_pid, this_tid, &wl);
346 			if (rc == CHG_SUCCESS) {
347 				return_now = 0;
348 				while (!chg.chg_wakeup) {
349 					if (wl->cleanup ||
350 					    door_client_dead()) {
351 						return_now = 1;
352 						break;
353 					}
354 					(void) cond_wait(&chg.chg_cv,
355 					    &chg.chg_lock);
356 				}
357 				/* Check if door client is still alive again */
358 				if (!return_now && !wl->cleanup &&
359 				    !door_client_dead()) {
360 					/* copy data to buffer */
361 					if ((info->str = malloc(
362 					    chg.chg_data_size)) == NULL) {
363 						rc = CHG_NO_MEMORY;
364 					} else {
365 						(void) memcpy(info->str,
366 						    chg.chg_data,
367 						    chg.chg_data_size);
368 						info->len = chg.chg_data_size;
369 					}
370 				}
371 				waiting_list_cleanup(&chg, this_tid);
372 			}
373 			(void) mutex_unlock(&chg.chg_lock);
374 		}
375 
376 
377 		/*
378 		 * Reset pid, tid and flag, send wakeup signal.
379 		 */
380 		(void) mutex_lock(&chg_main_nscd_lock);
381 		chg_main_nscd.pid = 0;
382 		chg_main_nscd.tid = 0;
383 		chg_main_nscd.in_progress = 0;
384 		if (chg_main_nscd.is_waiting_cleanup)
385 			(void) cond_broadcast(&chg_main_nscd_cv);
386 
387 		(void) mutex_unlock(&chg_main_nscd_lock);
388 
389 	} else if (in->ldap_u.get_change.op == NS_STATUS_CHANGE_OP_STOP) {
390 
391 		cleanup_thread_by_pid(nscd_pid);
392 		rc = CHG_SUCCESS;
393 
394 	} else {
395 		rc = CHG_INVALID_PARAM;
396 	}
397 	if (rc == CHG_EXCEED_MAX_THREADS)
398 		cleanup_thread_by_pid(0);
399 
400 	return (rc);
401 }
402 
403 /*
404  * This function copies the header and data stream to the buffer
405  * then send broadcast to wake up the chg_get_statusChange() threads.
406  */
407 int
408 chg_notify_statusChange(char *str)
409 {
410 	ldap_get_change_out_t *cout = (ldap_get_change_out_t *)str;
411 
412 	cout->cookie = chg_config_cookie_get();
413 
414 	(void) mutex_lock(&chg.chg_lock);
415 	if (chg.chg_w_first != NULL && chg.chg_wakeup == 0) {
416 
417 		if (chg.chg_data) {
418 			free(chg.chg_data);
419 			chg.chg_data = NULL;
420 		}
421 
422 		chg.chg_data = str;
423 
424 		if (cout->type == NS_STATUS_CHANGE_TYPE_CONFIG)
425 			chg.chg_data_size = sizeof (ldap_get_change_out_t);
426 		else
427 			/* NS_STATUS_CHANGE_TYPE_SERVER */
428 			chg.chg_data_size = sizeof (ldap_get_change_out_t) -
429 			    sizeof (int) + cout->data_size;
430 
431 		chg.chg_wakeup = 1;
432 		(void) cond_broadcast(&chg.chg_cv);
433 	}
434 	(void) mutex_unlock(&chg.chg_lock);
435 
436 	return (CHG_SUCCESS);
437 }
438 
439 /*
440  * This is called when the configuration is refreshed.
441  * The new configuration is different from the current one, a notification
442  * is sent tochg_get_statusChange() threads.
443  */
444 void
445 chg_test_config_change(ns_config_t *new, int *change_status)
446 {
447 	int	changed = 0;
448 	LineBuf	new_cfg, cur_cfg;
449 	ns_ldap_error_t *errp = NULL;
450 	ldap_config_out_t *new_out, *cur_out;
451 	ldap_get_change_out_t	*cout;
452 
453 	(void) memset(&new_cfg, 0, sizeof (LineBuf));
454 	(void) memset(&cur_cfg, 0, sizeof (LineBuf));
455 	/*
456 	 * Flatten the config data of the newly downloaded config and
457 	 * current default config and compare both.
458 	 */
459 	if ((errp = __ns_ldap_LoadDoorInfo(&new_cfg, NULL, new)) != NULL) {
460 		__ns_ldap_freeError(&errp);
461 		/* error, assume the config is changed */
462 		changed = 1;
463 	} else if ((errp = __ns_ldap_LoadDoorInfo(&cur_cfg, NULL, NULL))
464 	    != NULL) {
465 		__ns_ldap_freeError(&errp);
466 		/* error, assume the config is changed */
467 		changed = 1;
468 	}
469 	if (changed == 0) {
470 		new_out = (ldap_config_out_t *)new_cfg.str;
471 		cur_out = (ldap_config_out_t *)cur_cfg.str;
472 		if (strcmp(new_out->config_str, cur_out->config_str) != 0) {
473 			changed = 1;
474 			if (current_admin.debug_level >= DBG_PROFILE_REFRESH) {
475 				logit("config changed.\n");
476 			}
477 		}
478 	}
479 	if (cur_cfg.str)
480 		free(cur_cfg.str);
481 	if (new_cfg.str)
482 		free(new_cfg.str);
483 
484 	if (changed) {
485 
486 		if ((cout = calloc(1, sizeof (ldap_get_change_out_t)))
487 		    == NULL) {
488 			logit("chg_test_config_change: No Memory\n");
489 		} else {
490 			/*
491 			 * Replace the currentdefault config with the new
492 			 * config
493 			 */
494 			__s_api_init_config(new);
495 			chg_config_cookie_increment_seq_num();
496 			cout->type = NS_STATUS_CHANGE_TYPE_CONFIG;
497 			/*
498 			 * cout->cookie is set by
499 			 * chg_notify_statusChange
500 			 */
501 			(void) chg_notify_statusChange((char *)cout);
502 		}
503 	} else {
504 		__s_api_destroy_config(new);
505 	}
506 
507 	*change_status = changed;
508 }
509 
510 /*
511  * Wake up chg_get_statusChange() threads to clean up the threads
512  * that main nscd doesn't exist on the other of door anymore or
513  * the thread is marked as cleanup.
514  */
515 static void
516 cleanup_threads(chg_info_t *chg, pid_t pid, cleanup_type_t type)
517 {
518 	(void) mutex_lock(&chg->chg_lock);
519 	if (type == CLEANUP_BY_PID)
520 		waiting_list_set_cleanup(chg, pid);
521 	/*
522 	 * wake up threads without setting chg.chg_wakeup.
523 	 * It's for cleanup purpose, not for notifying changes.
524 	 */
525 	(void) cond_broadcast(&chg->chg_cv);
526 	(void) mutex_unlock(&chg->chg_lock);
527 }
528 /*
529  * If arg is NULL, it loops forever,
530  * else it calls cleanup_threads once and exits.
531  */
532 void *
533 chg_cleanup_waiting_threads(void *arg)
534 {
535 	cleanup_op_t *op = (cleanup_op_t *)arg;
536 	cleanup_type_t type = 0;
537 	pid_t	pid;
538 	int	always = 1, waiting;
539 
540 	if (op == NULL) {
541 		waiting = 1;
542 		type = CLEANUP_ALL;
543 		pid = 0;
544 	} else {
545 		waiting = 0;
546 		type = op->type;
547 		pid = op->pid;
548 	}
549 
550 	while (always) {
551 		if (waiting)
552 			(void) sleep(CLEANUP_WAIT_TIME);
553 		cleanup_threads(&chg, pid, type);
554 		if (!waiting)
555 			break;
556 	}
557 
558 	if (op)
559 		free(op);
560 
561 	thr_exit(NULL);
562 	return (NULL);
563 }
564 /*
565  * The door server thead which has the door client pid will be marked
566  * as to be clean up. If pid is 0, no marking and just clean up all.
567  */
568 static void
569 cleanup_thread_by_pid(pid_t pid)
570 {
571 	cleanup_op_t *op;
572 
573 	if ((op = malloc(sizeof (cleanup_op_t))) == NULL)
574 		return;
575 
576 	op->pid = pid;
577 	/* clean up all if pid is 0 */
578 	if (pid == 0)
579 		op->type = CLEANUP_ALL;
580 	else
581 		op->type = CLEANUP_BY_PID;
582 
583 	if (thr_create(NULL, 0, chg_cleanup_waiting_threads,
584 	    (void *)op, THR_BOUND|THR_DETACHED, NULL) != 0) {
585 		free(op);
586 		logit("thr_create failed for cleanup_thread_by_pid(%ld)\n",
587 		    pid);
588 	}
589 }
590 
591 /*
592  * Output a psinfo of an nscd process with process id pid
593  * Return: 0  - Can't find the process or it's not nscd
594  *         1  - psinfo found
595  * Note: If info is NULL, returns 0 or 1 only and no output from info.
596  */
597 static int
598 get_nscd_psinfo(pid_t pid, psinfo_t *info)
599 {
600 	psinfo_t	pinfo;
601 	char		fname[MAXPATHLEN];
602 	ssize_t		ret;
603 	int		fd;
604 
605 	if (snprintf(fname, MAXPATHLEN, "/proc/%d/psinfo", pid) > 0) {
606 		if ((fd = open(fname,  O_RDONLY)) >= 0) {
607 			ret = read(fd, &pinfo, sizeof (psinfo_t));
608 			(void) close(fd);
609 			if ((ret == sizeof (psinfo_t)) &&
610 			    (strcmp(pinfo.pr_fname, "nscd") == 0)) {
611 				if (info)
612 					*info = pinfo;
613 				return (1);
614 			}
615 		}
616 	}
617 	return (0);
618 }
619 /*
620  * If the parent process is nscd and euid is 0, it's a peruser nscd.
621  */
622 static int
623 is_peruser_nscd(pid_t pid)
624 {
625 	pid_t	ppid;
626 	psinfo_t pinfo;
627 
628 	if (get_nscd_psinfo(pid, &pinfo)) {
629 		ppid = pinfo.pr_ppid;
630 		if (get_nscd_psinfo(ppid, &pinfo) && pinfo.pr_euid == 0)
631 			/*
632 			 * get psinfo of parent forker nscd
633 			 */
634 			return (1);
635 		else
636 			return (0);
637 	} else {
638 		return (0);
639 	}
640 }
641 /*
642  * Check if the door client making door call is a nscd or peruser nscd and
643  * output door client's pid.
644  */
645 int
646 chg_is_called_from_nscd_or_peruser_nscd(char *dc_str, pid_t *pidp)
647 {
648 	int	rc;
649 	uid_t	euid;
650 	pid_t	pid;
651 	ucred_t	*uc = NULL;
652 
653 	if (door_ucred(&uc) != 0) {
654 		rc = errno;
655 		logit("door_ucred() call failed %s\n", strerror(rc));
656 		return (0);
657 	}
658 	euid = ucred_geteuid(uc);
659 	pid = *pidp = ucred_getpid(uc);
660 
661 	if ((euid == 0 && is_called_from_nscd(pid)) ||
662 	    is_peruser_nscd(pid)) {
663 		if (current_admin.debug_level >= DBG_ALL)
664 			logit("ldap_cachemgr received %s call from pid %ld, "
665 			    "uid %u, euid %u\n", dc_str, pid,
666 			    ucred_getruid(uc), euid);
667 		rc = 1;
668 	} else {
669 		if (current_admin.debug_level >= DBG_CANT_FIND)
670 			logit("%s call failed(cred): caller pid %ld, uid %u, "
671 			    "euid %u\n", dc_str, pid,
672 			    ucred_getruid(uc), euid);
673 
674 		rc = 0;
675 	}
676 
677 	ucred_free(uc);
678 
679 	return (rc);
680 }
681