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