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