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