| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| /* |
| * Copyright 2009 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| * |
| * Copyright 2018 Joyent, Inc. |
| */ |
| |
| #include <strings.h> |
| #include <stdlib.h> |
| #include <syslog.h> |
| #include <errno.h> |
| #include <libintl.h> |
| #include <door.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <procfs.h> |
| #include <pthread.h> |
| #include "cachemgr.h" |
| |
| extern admin_t current_admin; |
| |
| #define CLEANUP_WAIT_TIME 60 |
| |
| typedef enum cleanup_type { |
| CLEANUP_ALL = 1, |
| CLEANUP_BY_PID = 2 |
| } cleanup_type_t; |
| |
| typedef struct cleanup_op { |
| pid_t pid; |
| cleanup_type_t type; |
| } cleanup_op_t; |
| |
| typedef struct main_nscd_struct { |
| pid_t pid; /* main nscd pid */ |
| thread_t tid; /* main nscd tid */ |
| int in_progress; /* A main nscd thread is */ |
| /* waiting for change or */ |
| /* copying data */ |
| int is_waiting_cleanup; /* A main nscd thread is */ |
| /* waiting for another main */ |
| /* nscd thread to be cleaned */ |
| /* up */ |
| } main_nscd_t; |
| |
| static chg_info_t chg = { DEFAULTMUTEX, DEFAULTCV, 0, NULL, NULL, NULL, 0 }; |
| |
| static main_nscd_t chg_main_nscd = {0, 0, 0, 0}; |
| static mutex_t chg_main_nscd_lock = DEFAULTMUTEX; |
| static cond_t chg_main_nscd_cv = DEFAULTCV; |
| |
| /* |
| * The cookie of the configuration and its mutex |
| */ |
| static ldap_get_chg_cookie_t config_cookie = {0, 0}; |
| static mutex_t config_cookie_lock = DEFAULTMUTEX; |
| |
| static void cleanup_thread_by_pid(pid_t pid); |
| |
| ldap_get_chg_cookie_t |
| chg_config_cookie_get(void) |
| { |
| ldap_get_chg_cookie_t cookie; |
| (void) mutex_lock(&config_cookie_lock); |
| cookie = config_cookie; |
| (void) mutex_unlock(&config_cookie_lock); |
| return (cookie); |
| } |
| |
| static void |
| chg_config_cookie_increment_seq_num(void) |
| { |
| (void) mutex_lock(&config_cookie_lock); |
| config_cookie.seq_num++; |
| (void) mutex_unlock(&config_cookie_lock); |
| } |
| |
| void |
| chg_config_cookie_set(ldap_get_chg_cookie_t *cookie) |
| { |
| (void) mutex_lock(&config_cookie_lock); |
| config_cookie.mgr_pid = cookie->mgr_pid; |
| config_cookie.seq_num = cookie->seq_num; |
| (void) mutex_unlock(&config_cookie_lock); |
| } |
| static boolean_t |
| chg_cookie_equal(ldap_get_chg_cookie_t *c1, ldap_get_chg_cookie_t *c2) |
| { |
| if (c1->mgr_pid == c2->mgr_pid && c1->seq_num == c2->seq_num) |
| return (B_TRUE); |
| else |
| return (B_FALSE); |
| } |
| /* |
| * Create a node in the list and output the node. The caller can NOT free it. |
| */ |
| static int |
| waiting_list_add(chg_info_t *info, pid_t pid, thread_t tid, |
| waiting_list_t **wlp) |
| { |
| |
| waiting_list_t *wl; |
| |
| *wlp = NULL; |
| |
| if ((wl = (waiting_list_t *)calloc(1, sizeof (waiting_list_t))) |
| == NULL) { |
| logit("waiting_list_add: No memory. pid %ld tid %d\n", |
| pid, tid); |
| return (CHG_NO_MEMORY); |
| } |
| |
| wl->pid = pid; |
| wl->tid = tid; |
| |
| if (info->chg_w_first == NULL) { |
| info->chg_w_first = wl; |
| info->chg_w_last = wl; |
| } else { |
| info->chg_w_last->next = wl; |
| wl->prev = info->chg_w_last; |
| info->chg_w_last = wl; |
| } |
| *wlp = wl; |
| return (CHG_SUCCESS); |
| } |
| |
| /* |
| * Find a node with matching tid in the list and remove it from the list. |
| */ |
| static int |
| waiting_list_delete(chg_info_t *info, thread_t tid) |
| { |
| waiting_list_t *wl; |
| |
| for (wl = info->chg_w_first; wl != NULL; wl = wl->next) { |
| if (wl->tid == tid) { |
| if (wl->next == NULL) { |
| if (wl->prev == NULL) { |
| info->chg_w_first = NULL; |
| info->chg_w_last = NULL; |
| } else { |
| wl->prev->next = NULL; |
| info->chg_w_last = wl->prev; |
| } |
| } else { |
| if (wl->prev == NULL) { |
| wl->next->prev = NULL; |
| info->chg_w_first = wl->next; |
| } else { |
| wl->prev->next = wl->next; |
| wl->next->prev = wl->prev; |
| } |
| } |
| free(wl); |
| return (CHG_SUCCESS); |
| } |
| } |
| return (CHG_NOT_FOUND_IN_WAITING_LIST); |
| } |
| |
| /* |
| * Delete the thread from the waiting list and remove data when the list |
| * is empty. |
| */ |
| static void |
| waiting_list_cleanup(chg_info_t *chg, thread_t tid) |
| { |
| int rc; |
| |
| rc = waiting_list_delete(chg, tid); |
| |
| if (rc == CHG_SUCCESS && chg->chg_w_first == NULL) { |
| free(chg->chg_data); |
| chg->chg_data = NULL; |
| chg->chg_wakeup = 0; |
| } |
| } |
| |
| /* |
| * Set flag by pid so it can be cleaned up. |
| */ |
| static void |
| waiting_list_set_cleanup(chg_info_t *info, pid_t pid) |
| { |
| waiting_list_t *wl; |
| |
| for (wl = info->chg_w_first; wl != NULL; wl = wl->next) { |
| if (wl->pid == pid) { |
| wl->cleanup = 1; |
| break; |
| } |
| } |
| } |
| |
| /* |
| * Return: 1 - door client is dead, 0 - door client is alive |
| */ |
| static int |
| door_client_dead(void) |
| { |
| ucred_t *uc = NULL; |
| int rc; |
| |
| if (door_ucred(&uc) == -1 && errno == EINVAL) { |
| rc = 1; |
| } else { |
| rc = 0; |
| } |
| if (uc) |
| ucred_free(uc); |
| |
| return (rc); |
| } |
| |
| /* |
| * This function handles GETSTATUSCHANGE call from main nscd. |
| * The call can be a START op or STOP op. A cookie is sent from main nscd too. |
| * The static global variable main_nscd keeps record of pid, tid and some flags. |
| * If the thread is door_return(), main_nscd.pid, main_nscd.tid are set to 0. |
| * When the call is START op, it checks if main_nscd.pid is 0. If it is, it |
| * proceeds to wait for the change notification. If it's not, which means |
| * another main nscd handling thread is still around. It sends broadcast to |
| * clean up that thread and wait until the cleanup is done then proceeds to |
| * wait for the change notification. If same main nscd sends START op |
| * repeatedly, it'll be rejected. |
| * It also checks the cookie from main nscd. If it's not the same as |
| * ldap_cachemgr's cookie, door returns config change. |
| * If the door call is STOP op, it creates a thread to clean up main nscd START |
| * thread so it won't be blocking. |
| * In waiting for the change notification phase, the thread is waken up by |
| * the notification threads or by the cleanup threads. |
| * If it's a notification, it copies data to the stack then door return. |
| * If it's a cleanup, door_client_dead() is called to verify it then |
| * door return. |
| */ |
| int |
| chg_get_statusChange(LineBuf *info, ldap_call_t *in, pid_t nscd_pid) |
| { |
| int rc = CHG_SUCCESS, another_main_nscd_thread_alive = 0; |
| int len, return_now; |
| thread_t this_tid = thr_self(); |
| waiting_list_t *wl = NULL; |
| ldap_get_change_out_t *cout; |
| ldap_get_chg_cookie_t cookie; |
| |
| info->str = NULL; |
| info->len = 0; |
| |
| if (in->ldap_u.get_change.op == NS_STATUS_CHANGE_OP_START) { |
| |
| (void) mutex_lock(&chg_main_nscd_lock); |
| if (chg_main_nscd.pid != 0) { |
| if (nscd_pid != chg_main_nscd.pid) { |
| /* |
| * This is the case that nscd doesn't shut down |
| * properly(e.g. core) and STOP op is not sent, |
| * the thread handling it is still around and |
| * not cleaned up yet. |
| * Test if the thread is still alive. |
| * If it is, clean it up. |
| * For thr_kill, if sig is 0, a validity check |
| * is done for the existence of the target |
| * thread; no signal is sent. |
| */ |
| if (thr_kill(chg_main_nscd.tid, 0) == 0) { |
| another_main_nscd_thread_alive = 1; |
| cleanup_thread_by_pid( |
| chg_main_nscd.pid); |
| } |
| } else if (chg_main_nscd.in_progress || |
| chg_main_nscd.is_waiting_cleanup) { |
| /* |
| * Same nscd pid can only send door call |
| * one at a time and wait for ldap_cachemgr to |
| * return change data. If it's the same pid |
| * again, it's an nscd error. |
| */ |
| (void) mutex_unlock(&chg_main_nscd_lock); |
| return (CHG_NSCD_REPEATED_CALL); |
| } |
| } |
| /* |
| * Wait for another thread to be cleaned up if it's alive. |
| * After that this cond var is waken up. |
| */ |
| if (another_main_nscd_thread_alive) { |
| while (chg_main_nscd.in_progress) { |
| chg_main_nscd.is_waiting_cleanup = 1; |
| (void) cond_wait(&chg_main_nscd_cv, |
| &chg_main_nscd_lock); |
| } |
| } |
| |
| /* |
| * Replace pid and tid and set the flag. |
| */ |
| chg_main_nscd.is_waiting_cleanup = 0; |
| chg_main_nscd.pid = nscd_pid; |
| chg_main_nscd.tid = this_tid; |
| chg_main_nscd.in_progress = 1; |
| (void) mutex_unlock(&chg_main_nscd_lock); |
| |
| cookie = chg_config_cookie_get(); |
| |
| if (!chg_cookie_equal(&cookie, &in->ldap_u.get_change.cookie)) { |
| /* |
| * different cookie, set new cookie and |
| * return door call right away |
| */ |
| len = sizeof (ldap_get_change_out_t); |
| if ((cout = calloc(1, len)) == NULL) { |
| rc = CHG_NO_MEMORY; |
| } else { |
| cout->type = NS_STATUS_CHANGE_TYPE_CONFIG; |
| cout->cookie = cookie; |
| info->str = (char *)cout; |
| info->len = len; |
| } |
| |
| } else { |
| (void) mutex_lock(&chg.chg_lock); |
| |
| /* wait for the change notification */ |
| rc = waiting_list_add(&chg, nscd_pid, this_tid, &wl); |
| if (rc == CHG_SUCCESS) { |
| return_now = 0; |
| while (!chg.chg_wakeup) { |
| if (wl->cleanup || |
| door_client_dead()) { |
| return_now = 1; |
| break; |
| } |
| (void) cond_wait(&chg.chg_cv, |
| &chg.chg_lock); |
| } |
| /* Check if door client is still alive again */ |
| if (!return_now && !wl->cleanup && |
| !door_client_dead()) { |
| /* copy data to buffer */ |
| if ((info->str = malloc( |
| chg.chg_data_size)) == NULL) { |
| rc = CHG_NO_MEMORY; |
| } else { |
| (void) memcpy(info->str, |
| chg.chg_data, |
| chg.chg_data_size); |
| info->len = chg.chg_data_size; |
| } |
| } |
| waiting_list_cleanup(&chg, this_tid); |
| } |
| (void) mutex_unlock(&chg.chg_lock); |
| } |
| |
| |
| /* |
| * Reset pid, tid and flag, send wakeup signal. |
| */ |
| (void) mutex_lock(&chg_main_nscd_lock); |
| chg_main_nscd.pid = 0; |
| chg_main_nscd.tid = 0; |
| chg_main_nscd.in_progress = 0; |
| if (chg_main_nscd.is_waiting_cleanup) |
| (void) cond_broadcast(&chg_main_nscd_cv); |
| |
| (void) mutex_unlock(&chg_main_nscd_lock); |
| |
| } else if (in->ldap_u.get_change.op == NS_STATUS_CHANGE_OP_STOP) { |
| |
| cleanup_thread_by_pid(nscd_pid); |
| rc = CHG_SUCCESS; |
| |
| } else { |
| rc = CHG_INVALID_PARAM; |
| } |
| if (rc == CHG_EXCEED_MAX_THREADS) |
| cleanup_thread_by_pid(0); |
| |
| return (rc); |
| } |
| |
| /* |
| * This function copies the header and data stream to the buffer |
| * then send broadcast to wake up the chg_get_statusChange() threads. |
| */ |
| int |
| chg_notify_statusChange(char *str) |
| { |
| ldap_get_change_out_t *cout = (ldap_get_change_out_t *)str; |
| |
| cout->cookie = chg_config_cookie_get(); |
| |
| (void) mutex_lock(&chg.chg_lock); |
| if (chg.chg_w_first != NULL && chg.chg_wakeup == 0) { |
| |
| if (chg.chg_data) { |
| free(chg.chg_data); |
| chg.chg_data = NULL; |
| } |
| |
| chg.chg_data = str; |
| |
| if (cout->type == NS_STATUS_CHANGE_TYPE_CONFIG) |
| chg.chg_data_size = sizeof (ldap_get_change_out_t); |
| else |
| /* NS_STATUS_CHANGE_TYPE_SERVER */ |
| chg.chg_data_size = sizeof (ldap_get_change_out_t) - |
| sizeof (int) + cout->data_size; |
| |
| chg.chg_wakeup = 1; |
| (void) cond_broadcast(&chg.chg_cv); |
| } |
| (void) mutex_unlock(&chg.chg_lock); |
| |
| return (CHG_SUCCESS); |
| } |
| |
| /* |
| * This is called when the configuration is refreshed. |
| * The new configuration is different from the current one, a notification |
| * is sent tochg_get_statusChange() threads. |
| */ |
| void |
| chg_test_config_change(ns_config_t *new, int *change_status) |
| { |
| int changed = 0; |
| LineBuf new_cfg, cur_cfg; |
| ns_ldap_error_t *errp = NULL; |
| ldap_config_out_t *new_out, *cur_out; |
| ldap_get_change_out_t *cout; |
| |
| (void) memset(&new_cfg, 0, sizeof (LineBuf)); |
| (void) memset(&cur_cfg, 0, sizeof (LineBuf)); |
| /* |
| * Flatten the config data of the newly downloaded config and |
| * current default config and compare both. |
| */ |
| if ((errp = __ns_ldap_LoadDoorInfo(&new_cfg, NULL, new, 0)) != NULL) { |
| __ns_ldap_freeError(&errp); |
| /* error, assume the config is changed */ |
| changed = 1; |
| } else if ((errp = __ns_ldap_LoadDoorInfo(&cur_cfg, NULL, NULL, 0)) |
| != NULL) { |
| __ns_ldap_freeError(&errp); |
| /* error, assume the config is changed */ |
| changed = 1; |
| } |
| if (changed == 0) { |
| new_out = (ldap_config_out_t *)new_cfg.str; |
| cur_out = (ldap_config_out_t *)cur_cfg.str; |
| if (strcmp(new_out->config_str, cur_out->config_str) != 0) { |
| changed = 1; |
| if (current_admin.debug_level >= DBG_PROFILE_REFRESH) { |
| logit("config changed.\n"); |
| } |
| } |
| } |
| if (cur_cfg.str) |
| free(cur_cfg.str); |
| if (new_cfg.str) |
| free(new_cfg.str); |
| |
| if (changed) { |
| |
| if ((cout = calloc(1, sizeof (ldap_get_change_out_t))) |
| == NULL) { |
| logit("chg_test_config_change: No Memory\n"); |
| } else { |
| /* |
| * Replace the currentdefault config with the new |
| * config |
| */ |
| __s_api_init_config(new); |
| chg_config_cookie_increment_seq_num(); |
| cout->type = NS_STATUS_CHANGE_TYPE_CONFIG; |
| /* |
| * cout->cookie is set by |
| * chg_notify_statusChange |
| */ |
| (void) chg_notify_statusChange((char *)cout); |
| } |
| } else { |
| __s_api_destroy_config(new); |
| } |
| |
| *change_status = changed; |
| } |
| |
| /* |
| * Wake up chg_get_statusChange() threads to clean up the threads |
| * that main nscd doesn't exist on the other of door anymore or |
| * the thread is marked as cleanup. |
| */ |
| static void |
| cleanup_threads(chg_info_t *chg, pid_t pid, cleanup_type_t type) |
| { |
| (void) mutex_lock(&chg->chg_lock); |
| if (type == CLEANUP_BY_PID) |
| waiting_list_set_cleanup(chg, pid); |
| /* |
| * wake up threads without setting chg.chg_wakeup. |
| * It's for cleanup purpose, not for notifying changes. |
| */ |
| (void) cond_broadcast(&chg->chg_cv); |
| (void) mutex_unlock(&chg->chg_lock); |
| } |
| /* |
| * If arg is NULL, it loops forever, |
| * else it calls cleanup_threads once and exits. |
| */ |
| void * |
| chg_cleanup_waiting_threads(void *arg) |
| { |
| cleanup_op_t *op = (cleanup_op_t *)arg; |
| cleanup_type_t type = 0; |
| pid_t pid; |
| int always = 1, waiting; |
| |
| (void) pthread_setname_np(pthread_self(), "chg_cleanup_thr"); |
| |
| if (op == NULL) { |
| waiting = 1; |
| type = CLEANUP_ALL; |
| pid = 0; |
| } else { |
| waiting = 0; |
| type = op->type; |
| pid = op->pid; |
| } |
| |
| while (always) { |
| if (waiting) |
| (void) sleep(CLEANUP_WAIT_TIME); |
| cleanup_threads(&chg, pid, type); |
| if (!waiting) |
| break; |
| } |
| |
| if (op) |
| free(op); |
| |
| thr_exit(NULL); |
| return (NULL); |
| } |
| /* |
| * The door server thead which has the door client pid will be marked |
| * as to be clean up. If pid is 0, no marking and just clean up all. |
| */ |
| static void |
| cleanup_thread_by_pid(pid_t pid) |
| { |
| cleanup_op_t *op; |
| |
| if ((op = malloc(sizeof (cleanup_op_t))) == NULL) |
| return; |
| |
| op->pid = pid; |
| /* clean up all if pid is 0 */ |
| if (pid == 0) |
| op->type = CLEANUP_ALL; |
| else |
| op->type = CLEANUP_BY_PID; |
| |
| if (thr_create(NULL, 0, chg_cleanup_waiting_threads, |
| (void *)op, THR_BOUND|THR_DETACHED, NULL) != 0) { |
| free(op); |
| logit("thr_create failed for cleanup_thread_by_pid(%ld)\n", |
| pid); |
| } |
| } |
| |
| /* |
| * Output a psinfo of an nscd process with process id pid |
| * Return: 0 - Can't find the process or it's not nscd |
| * 1 - psinfo found |
| * Note: If info is NULL, returns 0 or 1 only and no output from info. |
| */ |
| static int |
| get_nscd_psinfo(pid_t pid, psinfo_t *info) |
| { |
| psinfo_t pinfo; |
| char fname[MAXPATHLEN]; |
| ssize_t ret; |
| int fd; |
| |
| if (snprintf(fname, MAXPATHLEN, "/proc/%d/psinfo", pid) > 0) { |
| if ((fd = open(fname, O_RDONLY)) >= 0) { |
| ret = read(fd, &pinfo, sizeof (psinfo_t)); |
| (void) close(fd); |
| if ((ret == sizeof (psinfo_t)) && |
| (strcmp(pinfo.pr_fname, "nscd") == 0)) { |
| if (info) |
| *info = pinfo; |
| return (1); |
| } |
| } |
| } |
| return (0); |
| } |
| /* |
| * If the parent process is nscd and euid is 0, it's a peruser nscd. |
| */ |
| static int |
| is_peruser_nscd(pid_t pid) |
| { |
| pid_t ppid; |
| psinfo_t pinfo; |
| |
| if (get_nscd_psinfo(pid, &pinfo)) { |
| ppid = pinfo.pr_ppid; |
| if (get_nscd_psinfo(ppid, &pinfo) && pinfo.pr_euid == 0) |
| /* |
| * get psinfo of parent forker nscd |
| */ |
| return (1); |
| else |
| return (0); |
| } else { |
| return (0); |
| } |
| } |
| /* |
| * Check if the door client making door call is a nscd or peruser nscd and |
| * output door client's pid. |
| */ |
| int |
| chg_is_called_from_nscd_or_peruser_nscd(char *dc_str, pid_t *pidp) |
| { |
| int rc; |
| uid_t euid; |
| pid_t pid; |
| ucred_t *uc = NULL; |
| |
| if (door_ucred(&uc) != 0) { |
| rc = errno; |
| logit("door_ucred() call failed %s\n", strerror(rc)); |
| return (0); |
| } |
| euid = ucred_geteuid(uc); |
| pid = *pidp = ucred_getpid(uc); |
| |
| if ((euid == 0 && is_called_from_nscd(pid)) || |
| is_peruser_nscd(pid)) { |
| if (current_admin.debug_level >= DBG_ALL) |
| logit("ldap_cachemgr received %s call from pid %ld, " |
| "uid %u, euid %u\n", dc_str, pid, |
| ucred_getruid(uc), euid); |
| rc = 1; |
| } else { |
| if (current_admin.debug_level >= DBG_CANT_FIND) |
| logit("%s call failed(cred): caller pid %ld, uid %u, " |
| "euid %u\n", dc_str, pid, |
| ucred_getruid(uc), euid); |
| |
| rc = 0; |
| } |
| |
| ucred_free(uc); |
| |
| return (rc); |
| } |