/* * 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * * Copyright 2018 Joyent, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* tolower */ #include #include #include #include #include "cachemgr.h" #include "solaris-priv.h" #include "ns_connmgmt.h" static rwlock_t ldap_lock = DEFAULTRWLOCK; static int sighup_update = FALSE; extern admin_t current_admin; extern int is_root_or_all_privs(char *dc_str, ucred_t **ucp); /* variables used for SIGHUP wakeup on sleep */ static mutex_t sighuplock; static cond_t cond; /* refresh time statistics */ static time_t prev_refresh_time = 0; /* variables used for signaling parent process */ static mutex_t sig_mutex; static int signal_done = FALSE; /* TCP connection timeout (in milliseconds) */ static int tcptimeout = NS_DEFAULT_BIND_TIMEOUT * 1000; #ifdef SLP extern int use_slp; #endif /* SLP */ /* nis domain information */ #define _NIS_FILTER "objectclass=nisDomainObject" #define _NIS_DOMAIN "nisdomain" #define CACHESLEEPTIME 600 /* * server list refresh delay when in "no server" mode * (1 second) */ #define REFRESH_DELAY_WHEN_NO_SERVER 1 typedef enum { INFO_OP_CREATE = 0, INFO_OP_DELETE = 1, INFO_OP_REFRESH = 2, INFO_OP_REFRESH_WAIT = 3, INFO_OP_GETSERVER = 4, INFO_OP_GETSTAT = 5, INFO_OP_REMOVESERVER = 6 } info_op_t; typedef enum { INFO_RW_UNKNOWN = 0, INFO_RW_READONLY = 1, INFO_RW_WRITEABLE = 2 } info_rw_t; typedef enum { INFO_SERVER_JUST_INITED = -1, INFO_SERVER_UNKNOWN = 0, INFO_SERVER_CONNECTING = 1, INFO_SERVER_UP = 2, INFO_SERVER_ERROR = 3, INFO_SERVER_REMOVED = 4 } info_server_t; typedef enum { INFO_STATUS_UNKNOWN = 0, INFO_STATUS_ERROR = 1, INFO_STATUS_NEW = 2, INFO_STATUS_OLD = 3 } info_status_t; typedef enum { CACHE_OP_CREATE = 0, CACHE_OP_DELETE = 1, CACHE_OP_FIND = 2, CACHE_OP_ADD = 3, CACHE_OP_GETSTAT = 4 } cache_op_t; typedef enum { CACHE_MAP_UNKNOWN = 0, CACHE_MAP_DN2DOMAIN = 1 } cache_type_t; typedef struct server_info_ext { char *addr; char *hostname; char *rootDSE_data; char *errormsg; info_rw_t type; info_server_t server_status; info_server_t prev_server_status; info_status_t info_status; ns_server_status_t change; } server_info_ext_t; typedef struct server_info { struct server_info *next; mutex_t mutex[2]; /* 0: current copy lock */ /* 1: update copy lock */ server_info_ext_t sinfo[2]; /* 0: current, 1: update copy */ } server_info_t; typedef struct cache_hash { cache_type_t type; char *from; char *to; struct cache_hash *next; } cache_hash_t; /* * The status of a server to be removed. It can be up or down. */ typedef struct rm_svr { char *addr; int up; /* 1: up, 0: down */ } rm_svr_t; static int getldap_destroy_serverInfo(server_info_t *head); static void test_server_change(server_info_t *head); static void remove_server(char *addr); static ns_server_status_t set_server_status(char *input, server_info_t *head); static void create_buf_and_notify(char *input, ns_server_status_t st); /* * Load configuration * The code was in signal handler getldap_revalidate * It's moved out of the handler because it could cause deadlock * return: 1 SUCCESS * 0 FAIL */ static int load_config(void) { ns_ldap_error_t *error; int rc = 1; (void) __ns_ldap_setServer(TRUE); (void) rw_wrlock(&ldap_lock); if ((error = __ns_ldap_LoadConfiguration()) != NULL) { logit("Error: Unable to read '%s': %s\n", NSCONFIGFILE, error->message); __ns_ldap_freeError(&error); rc = 0; /* FAIL */ } else sighup_update = TRUE; (void) rw_unlock(&ldap_lock); return (rc); } /* * Calculate a hash for a string * Based on elf_hash algorithm, hash is case insensitive * Uses tolower instead of _tolower because of I18N */ static unsigned long getldap_hash(const char *str) { unsigned int hval = 0; while (*str) { unsigned int g; hval = (hval << 4) + tolower(*str++); if ((g = (hval & 0xf0000000)) != 0) hval ^= g >> 24; hval &= ~g; } return ((unsigned long)hval); } /* * Remove a hash table entry. * This function expects a lock in place when called. */ static cache_hash_t * getldap_free_hash(cache_hash_t *p) { cache_hash_t *next; p->type = CACHE_MAP_UNKNOWN; if (p->from) free(p->from); if (p->to) free(p->to); next = p->next; p->next = NULL; free(p); return (next); } /* * Scan a hash table hit for a matching hash entry. * This function expects a lock in place when called. */ static cache_hash_t * getldap_scan_hash(cache_type_t type, char *from, cache_hash_t *idx) { while (idx) { if (idx->type == type && strcasecmp(from, idx->from) == 0) { return (idx); } idx = idx->next; } return ((cache_hash_t *)NULL); } /* * Format and return the cache data statistics */ static int getldap_get_cacheData_stat(int max, int current, char **output) { #define C_HEADER0 "Cache data information: " #define C_HEADER1 " Maximum cache entries: " #define C_HEADER2 " Number of cache entries: " int hdr0_len = strlen(gettext(C_HEADER0)); int hdr1_len = strlen(gettext(C_HEADER1)); int hdr2_len = strlen(gettext(C_HEADER2)); int len; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_cacheData_stat()...\n"); } *output = NULL; len = hdr0_len + hdr1_len + hdr2_len + 3 * strlen(DOORLINESEP) + 21; *output = malloc(len); if (*output == NULL) return (-1); (void) snprintf(*output, len, "%s%s%s%10d%s%s%10d%s", gettext(C_HEADER0), DOORLINESEP, gettext(C_HEADER1), max, DOORLINESEP, gettext(C_HEADER2), current, DOORLINESEP); return (NS_LDAP_SUCCESS); } static int getldap_cache_op(cache_op_t op, cache_type_t type, char *from, char **to) { #define CACHE_HASH_MAX 257 #define CACHE_HASH_MAX_ENTRY 256 static cache_hash_t *hashTbl[CACHE_HASH_MAX]; cache_hash_t *next, *idx, *newp; unsigned long hash; static rwlock_t cache_lock = DEFAULTRWLOCK; int i; static int entry_num = 0; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_cache_op()...\n"); } switch (op) { case CACHE_OP_CREATE: if (current_admin.debug_level >= DBG_ALL) { logit("operation is CACHE_OP_CREATE...\n"); } (void) rw_wrlock(&cache_lock); for (i = 0; i < CACHE_HASH_MAX; i++) { hashTbl[i] = NULL; } entry_num = 0; (void) rw_unlock(&cache_lock); break; case CACHE_OP_DELETE: if (current_admin.debug_level >= DBG_ALL) { logit("operation is CACHE_OP_DELETE...\n"); } (void) rw_wrlock(&cache_lock); for (i = 0; i < CACHE_HASH_MAX; i++) { next = hashTbl[i]; while (next != NULL) { next = getldap_free_hash(next); } hashTbl[i] = NULL; } entry_num = 0; (void) rw_unlock(&cache_lock); break; case CACHE_OP_ADD: if (current_admin.debug_level >= DBG_ALL) { logit("operation is CACHE_OP_ADD...\n"); } if (from == NULL || to == NULL || *to == NULL) return (-1); hash = getldap_hash(from) % CACHE_HASH_MAX; (void) rw_wrlock(&cache_lock); idx = hashTbl[hash]; /* * replace old "to" value with new one * if an entry with same "from" * already exists */ if (idx) { newp = getldap_scan_hash(type, from, idx); if (newp) { free(newp->to); newp->to = strdup(*to); (void) rw_unlock(&cache_lock); return (NS_LDAP_SUCCESS); } } if (entry_num > CACHE_HASH_MAX_ENTRY) { (void) rw_unlock(&cache_lock); return (-1); } newp = (cache_hash_t *)malloc(sizeof (cache_hash_t)); if (newp == NULL) { (void) rw_unlock(&cache_lock); return (NS_LDAP_MEMORY); } newp->type = type; newp->from = strdup(from); newp->to = strdup(*to); newp->next = idx; hashTbl[hash] = newp; entry_num++; (void) rw_unlock(&cache_lock); break; case CACHE_OP_FIND: if (current_admin.debug_level >= DBG_ALL) { logit("operation is CACHE_OP_FIND...\n"); } if (from == NULL || to == NULL) return (-1); *to = NULL; hash = getldap_hash(from) % CACHE_HASH_MAX; (void) rw_rdlock(&cache_lock); idx = hashTbl[hash]; idx = getldap_scan_hash(type, from, idx); if (idx) *to = strdup(idx->to); (void) rw_unlock(&cache_lock); if (idx == NULL) return (-1); break; case CACHE_OP_GETSTAT: if (current_admin.debug_level >= DBG_ALL) { logit("operation is CACHE_OP_GETSTAT...\n"); } if (to == NULL) return (-1); return (getldap_get_cacheData_stat(CACHE_HASH_MAX_ENTRY, entry_num, to)); break; default: logit("getldap_cache_op(): " "invalid operation code (%d).\n", op); return (-1); break; } return (NS_LDAP_SUCCESS); } /* * Function: sync_current_with_update_copy * * This function syncs up the 2 sinfo copies in info. * * The 2 copies are identical most of time. * The update copy(sinfo[1]) could be different when * getldap_serverInfo_refresh thread is refreshing the server list * and calls getldap_get_rootDSE to update info. getldap_get_rootDSE * calls sync_current_with_update_copy to sync up 2 copies before thr_exit. * The calling sequence is * getldap_serverInfo_refresh-> * getldap_get_serverInfo_op(INFO_OP_CREATE,...)-> * getldap_set_serverInfo-> * getldap_get_rootDSE * * The original server_info_t has one copy of server info. When libsldap * makes door call GETLDAPSERVER to get the server info and getldap_get_rootDSE * is updating the server info, it would hit a unprotected window in * getldap_rootDSE. The door call will not get server info and libsldap * fails at making ldap connection. * * The new server_info_t provides GETLDAPSERVER thread with a current * copy(sinfo[0]). getldap_get_rootDSE only works on the update copy(sinfo[1]) * and syncs up 2 copies before thr_exit. This will close the window in * getldap_get_rootDSE. * */ static void sync_current_with_update_copy(server_info_t *info) { if (current_admin.debug_level >= DBG_ALL) { logit("sync_current_with_update_copy()...\n"); } (void) mutex_lock(&info->mutex[1]); (void) mutex_lock(&info->mutex[0]); if (info->sinfo[1].server_status == INFO_SERVER_UP && info->sinfo[0].server_status != INFO_SERVER_UP) info->sinfo[1].change = NS_SERVER_UP; else if (info->sinfo[1].server_status != INFO_SERVER_UP && info->sinfo[0].server_status == INFO_SERVER_UP) info->sinfo[1].change = NS_SERVER_DOWN; else info->sinfo[1].change = 0; /* free memory in current copy first */ if (info->sinfo[0].addr) free(info->sinfo[0].addr); info->sinfo[0].addr = NULL; if (info->sinfo[0].hostname) free(info->sinfo[0].hostname); info->sinfo[0].hostname = NULL; if (info->sinfo[0].rootDSE_data) free(info->sinfo[0].rootDSE_data); info->sinfo[0].rootDSE_data = NULL; if (info->sinfo[0].errormsg) free(info->sinfo[0].errormsg); info->sinfo[0].errormsg = NULL; /* * make current and update copy identical */ info->sinfo[0] = info->sinfo[1]; /* * getldap_get_server_stat() reads the update copy sinfo[1] * so it can't be freed or nullified yet at this point. * * The sinfo[0] and sinfo[1] have identical string pointers. * strdup the strings to avoid the double free problem. * The strings of sinfo[1] are freed in * getldap_get_rootDSE() and the strings of sinfo[0] * are freed earlier in this function. If the pointers are the * same, they will be freed twice. */ if (info->sinfo[1].addr) info->sinfo[0].addr = strdup(info->sinfo[1].addr); if (info->sinfo[1].hostname) info->sinfo[0].hostname = strdup(info->sinfo[1].hostname); if (info->sinfo[1].rootDSE_data) info->sinfo[0].rootDSE_data = strdup(info->sinfo[1].rootDSE_data); if (info->sinfo[1].errormsg) info->sinfo[0].errormsg = strdup(info->sinfo[1].errormsg); (void) mutex_unlock(&info->mutex[0]); (void) mutex_unlock(&info->mutex[1]); } static void * getldap_get_rootDSE(void *arg) { server_info_t *serverInfo = (server_info_t *)arg; char *rootDSE; int exitrc = NS_LDAP_SUCCESS; pid_t ppid; int server_found = 0; char errmsg[MAXERROR]; ns_ldap_return_code rc; ns_ldap_error_t *error = NULL; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_rootDSE()....\n"); } /* initialize the server info element */ (void) mutex_lock(&serverInfo->mutex[1]); serverInfo->sinfo[1].type = INFO_RW_UNKNOWN; serverInfo->sinfo[1].info_status = INFO_STATUS_UNKNOWN; /* * When the sever list is refreshed over and over, * this function is called each time it is refreshed. * The previous server status of the update copy(sinfo[1]) * is the status of the current copy */ (void) mutex_lock(&serverInfo->mutex[0]); serverInfo->sinfo[1].prev_server_status = serverInfo->sinfo[0].server_status; (void) mutex_unlock(&serverInfo->mutex[0]); serverInfo->sinfo[1].server_status = INFO_SERVER_UNKNOWN; if (serverInfo->sinfo[1].rootDSE_data) free(serverInfo->sinfo[1].rootDSE_data); serverInfo->sinfo[1].rootDSE_data = NULL; if (serverInfo->sinfo[1].errormsg) free(serverInfo->sinfo[1].errormsg); serverInfo->sinfo[1].errormsg = NULL; (void) mutex_unlock(&serverInfo->mutex[1]); (void) mutex_lock(&serverInfo->mutex[1]); serverInfo->sinfo[1].server_status = INFO_SERVER_CONNECTING; (void) mutex_unlock(&serverInfo->mutex[1]); /* * WARNING: anon_fallback == 1 (last argument) means that when * __ns_ldap_getRootDSE is unable to bind using the configured * credentials, it will try to fall back to using anonymous, non-SSL * mode of operation. * * This is for backward compatibility reasons - we might have machines * in the field with broken configuration (invalid credentials) and we * don't want them to be disturbed. */ if (rc = __ns_ldap_getRootDSE(serverInfo->sinfo[1].addr, &rootDSE, &error, SA_ALLOW_FALLBACK) != NS_LDAP_SUCCESS) { (void) mutex_lock(&serverInfo->mutex[1]); serverInfo->sinfo[1].server_status = INFO_SERVER_ERROR; serverInfo->sinfo[1].info_status = INFO_STATUS_ERROR; if (error && error->message) { serverInfo->sinfo[1].errormsg = strdup(error->message); } else { (void) snprintf(errmsg, sizeof (errmsg), "%s %s " "(rc = %d)", gettext("Can not get the root DSE from" " server"), serverInfo->sinfo[1].addr, rc); serverInfo->sinfo[1].errormsg = strdup(errmsg); } if (error != NULL) { (void) __ns_ldap_freeError(&error); } if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_rootDSE: %s.\n", serverInfo->sinfo[1].errormsg); } (void) mutex_unlock(&serverInfo->mutex[1]); /* * sync sinfo copies in the serverInfo. * protected by mutex */ sync_current_with_update_copy(serverInfo); thr_exit((void *) -1); } (void) mutex_lock(&serverInfo->mutex[1]); /* assume writeable, i.e., can do modify */ serverInfo->sinfo[1].type = INFO_RW_WRITEABLE; serverInfo->sinfo[1].server_status = INFO_SERVER_UP; serverInfo->sinfo[1].info_status = INFO_STATUS_NEW; /* remove the last DOORLINESEP */ *(rootDSE+strlen(rootDSE)-1) = '\0'; serverInfo->sinfo[1].rootDSE_data = rootDSE; server_found = 1; (void) mutex_unlock(&serverInfo->mutex[1]); /* * sync sinfo copies in the serverInfo. * protected by mutex */ sync_current_with_update_copy(serverInfo); /* * signal that the ldap_cachemgr parent process * should exit now, if it is still waiting */ (void) mutex_lock(&sig_mutex); if (signal_done == FALSE && server_found) { ppid = getppid(); (void) kill(ppid, SIGUSR1); if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_rootDSE(): " "SIGUSR1 signal sent to " "parent process(%ld).\n", ppid); } signal_done = TRUE; } (void) mutex_unlock(&sig_mutex); thr_exit((void *) exitrc); return ((void *) NULL); } static int getldap_init_serverInfo(server_info_t **head) { char **servers = NULL; int rc = 0, i, exitrc = NS_LDAP_SUCCESS; ns_ldap_error_t *errorp = NULL; server_info_t *info, *tail = NULL; *head = NULL; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_init_serverInfo()...\n"); } rc = __s_api_getServers(&servers, &errorp); if (rc != NS_LDAP_SUCCESS) { logit("getldap_init_serverInfo: " "__s_api_getServers failed.\n"); if (errorp) __ns_ldap_freeError(&errorp); return (-1); } for (i = 0; servers[i] != NULL; i++) { info = (server_info_t *)calloc(1, sizeof (server_info_t)); if (info == NULL) { logit("getldap_init_serverInfo: " "not enough memory.\n"); exitrc = NS_LDAP_MEMORY; break; } if (i == 0) { *head = info; tail = info; } else { tail->next = info; tail = info; } info->sinfo[0].addr = strdup(servers[i]); if (info->sinfo[0].addr == NULL) { logit("getldap_init_serverInfo: " "not enough memory.\n"); exitrc = NS_LDAP_MEMORY; break; } info->sinfo[1].addr = strdup(servers[i]); if (info->sinfo[1].addr == NULL) { logit("getldap_init_serverInfo: " "not enough memory.\n"); exitrc = NS_LDAP_MEMORY; break; } info->sinfo[0].type = INFO_RW_UNKNOWN; info->sinfo[1].type = INFO_RW_UNKNOWN; info->sinfo[0].info_status = INFO_STATUS_UNKNOWN; info->sinfo[1].info_status = INFO_STATUS_UNKNOWN; info->sinfo[0].server_status = INFO_SERVER_UNKNOWN; info->sinfo[1].server_status = INFO_SERVER_UNKNOWN; /* * Assume at startup or after the configuration * profile is refreshed, all servers are good. */ info->sinfo[0].prev_server_status = INFO_SERVER_UP; info->sinfo[1].prev_server_status = INFO_SERVER_UP; info->sinfo[0].hostname = NULL; info->sinfo[1].hostname = NULL; info->sinfo[0].rootDSE_data = NULL; info->sinfo[1].rootDSE_data = NULL; info->sinfo[0].errormsg = NULL; info->sinfo[1].errormsg = NULL; info->next = NULL; } __s_api_free2dArray(servers); if (exitrc != NS_LDAP_SUCCESS) { if (head && *head) { (void) getldap_destroy_serverInfo(*head); *head = NULL; } } return (exitrc); } static int getldap_destroy_serverInfo(server_info_t *head) { server_info_t *info, *next; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_destroy_serverInfo()...\n"); } if (head == NULL) { logit("getldap_destroy_serverInfo: " "invalid serverInfo list.\n"); return (-1); } for (info = head; info; info = next) { if (info->sinfo[0].addr) free(info->sinfo[0].addr); if (info->sinfo[1].addr) free(info->sinfo[1].addr); if (info->sinfo[0].hostname) free(info->sinfo[0].hostname); if (info->sinfo[1].hostname) free(info->sinfo[1].hostname); if (info->sinfo[0].rootDSE_data) free(info->sinfo[0].rootDSE_data); if (info->sinfo[1].rootDSE_data) free(info->sinfo[1].rootDSE_data); if (info->sinfo[0].errormsg) free(info->sinfo[0].errormsg); if (info->sinfo[1].errormsg) free(info->sinfo[1].errormsg); next = info->next; free(info); } return (NS_LDAP_SUCCESS); } static int getldap_set_serverInfo(server_info_t *head, int reset_bindtime, info_op_t op) { server_info_t *info; int atleast1 = 0; thread_t *tid; int num_threads = 0, i, j; void *status; void **paramVal = NULL; ns_ldap_error_t *error = NULL; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_set_serverInfo()...\n"); } if (head == NULL) { logit("getldap_set_serverInfo: " "invalid serverInfo list.\n"); return (-1); } /* Get the bind timeout value */ if (reset_bindtime == 1) { tcptimeout = NS_DEFAULT_BIND_TIMEOUT * 1000; (void) __ns_ldap_getParam(NS_LDAP_BIND_TIME_P, ¶mVal, &error); if (paramVal != NULL && *paramVal != NULL) { /* convert to milliseconds */ tcptimeout = **((int **)paramVal); tcptimeout *= 1000; (void) __ns_ldap_freeParam(¶mVal); } if (error) (void) __ns_ldap_freeError(&error); } for (info = head; info; info = info->next) num_threads++; if (num_threads == 0) { logit("getldap_set_serverInfo: " "empty serverInfo list.\n"); return (-1); } tid = (thread_t *) calloc(1, sizeof (thread_t) * num_threads); if (tid == NULL) { logit("getldap_set_serverInfo: " "No memory to create thread ID list.\n"); return (-1); } for (info = head, i = 0; info; info = info->next, i++) { if (thr_create(NULL, 0, (void *(*)(void*))getldap_get_rootDSE, (void *)info, 0, &tid[i])) { logit("getldap_set_serverInfo: " "can not create thread %d.\n", i + 1); for (j = 0; j < i; j++) (void) thr_join(tid[j], NULL, NULL); free(tid); return (-1); } } for (i = 0; i < num_threads; i++) { if (thr_join(tid[i], NULL, &status) == 0) { if ((int)status == NS_LDAP_SUCCESS) atleast1 = 1; } } free(tid); if (op == INFO_OP_REFRESH) test_server_change(head); if (atleast1) { return (NS_LDAP_SUCCESS); } else return (-1); } /* * getldap_get_serverInfo processes the GETLDAPSERVER door request passed * to this function from getldap_serverInfo_op(). * input: * a buffer containing an empty string (e.g., input[0]='\0';) or a string * as the "input" in printf(input, "%s%s%s%s", req, addrtype, DOORLINESEP, * addr); * where addr is the address of a server and * req is one of the following: * NS_CACHE_NEW: send a new server address, addr is ignored. * NS_CACHE_NORESP: send the next one, remove addr from list. * NS_CACHE_NEXT: send the next one, keep addr on list. * NS_CACHE_WRITE: send a non-replica server, if possible, if not, same * as NS_CACHE_NEXT. * addrtype: * NS_CACHE_ADDR_IP: return server address as is, this is default. * NS_CACHE_ADDR_HOSTNAME: return both server address and its FQDN format, * only self credential case requires such format. * output: * a buffer containing server info in the following format: * serveraddress DOORLINESEP [ serveraddress FQDN DOORLINESEP ] * [ attr=value [DOORLINESEP attr=value ]...] * For example: ( here | used as DOORLINESEP for visual purposes) * 1) simple bind and sasl/DIGEST-MD5 bind : * 1.2.3.4|supportedControl=1.1.1.1|supportedSASLmechanisms=EXTERNAL| * supportedSASLmechanisms=GSSAPI * 2) sasl/GSSAPI bind (self credential): * 1.2.3.4|foo.sun.com|supportedControl=1.1.1.1| * supportedSASLmechanisms=EXTERNAL|supportedSASLmechanisms=GSSAPI * NOTE: caller should free this buffer when done using it */ static int getldap_get_serverInfo(server_info_t *head, char *input, char **output, int *svr_removed) { server_info_t *info = NULL; server_info_t *server = NULL; char *addr = NULL; char *req = NULL; char req_new[] = NS_CACHE_NEW; char addr_type[] = NS_CACHE_ADDR_IP; int matched = FALSE, len = 0, rc = 0; char *ret_addr = NULL, *ret_addrFQDN = NULL; char *new_addr = NULL; pid_t pid; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_serverInfo()...\n"); } if (input == NULL || output == NULL) { logit("getldap_get_serverInfo: " "No input or output buffer.\n"); return (-1); } *output = NULL; *svr_removed = FALSE; if (head == NULL) { logit("getldap_get_serverInfo: " "invalid serverInfo list.\n"); return (-1); } /* * parse the input string to get req and addr, * if input is empty, i.e., input[0] == '\0', * treat it as an NS_CACHE_NEW request */ req = req_new; if (input[0] != '\0') { req = input; /* Save addr type flag */ addr_type[0] = input[1]; input[strlen(NS_CACHE_NEW)] = '\0'; /* skip acion type flag, addr type flag and DOORLINESEP */ addr = input + strlen(DOORLINESEP) + strlen(NS_CACHE_NEW) + strlen(NS_CACHE_ADDR_IP); } /* * if NS_CACHE_NEW, * or the server info is new, * starts from the * beginning of the list */ if ((strcmp(req, NS_CACHE_NEW) == 0) || (head->sinfo[0].info_status == INFO_STATUS_NEW)) matched = TRUE; for (info = head; info; info = info->next) { /* * make sure the server info stays the same * while the data is being processed */ /* * This function is called to get server info list * and pass it back to door call clients. * Access the current copy (sinfo[0]) to get such * information */ (void) mutex_lock(&info->mutex[0]); if (matched == FALSE && strcmp(info->sinfo[0].addr, addr) == 0) { matched = TRUE; if (strcmp(req, NS_CACHE_NORESP) == 0) { if (chg_is_called_from_nscd_or_peruser_nscd( "REMOVE SERVER", &pid) == 0) { (void) mutex_unlock(&info->mutex[0]); if (current_admin.debug_level >= DBG_ALL) logit("Only nscd can remove " "servers. pid %ld", pid); continue; } /* * if the information is new, * give this server one more chance */ if (info->sinfo[0].info_status == INFO_STATUS_NEW && info->sinfo[0].server_status == INFO_SERVER_UP) { server = info; break; } else { /* * it is recommended that * before removing the * server from the list, * the server should be * contacted one more time * to make sure that it is * really unavailable. * For now, just trust the client * (i.e., the sldap library) * that it knows what it is * doing and would not try * to mess up the server * list. */ /* * Make a copy of addr to contact * it later. It's not doing it here * to avoid long wait and possible * recursion to contact an LDAP server. */ new_addr = strdup(info->sinfo[0].addr); if (new_addr) remove_server(new_addr); *svr_removed = TRUE; (void) mutex_unlock(&info->mutex[0]); break; } } else { /* * req == NS_CACHE_NEXT or NS_CACHE_WRITE */ (void) mutex_unlock(&info->mutex[0]); continue; } } if (matched) { if (strcmp(req, NS_CACHE_WRITE) == 0) { if (info->sinfo[0].type == INFO_RW_WRITEABLE && info->sinfo[0].server_status == INFO_SERVER_UP) { server = info; break; } } else if (info->sinfo[0].server_status == INFO_SERVER_UP) { server = info; break; } } (void) mutex_unlock(&info->mutex[0]); } if (server) { if (strcmp(addr_type, NS_CACHE_ADDR_HOSTNAME) == 0) { /* * In SASL/GSSAPI case, a hostname is required for * Kerberos's service principal. * e.g. * ldap/foo.sun.com@SUN.COM */ if (server->sinfo[0].hostname == NULL) { rc = __s_api_ip2hostname(server->sinfo[0].addr, &server->sinfo[0].hostname); if (rc != NS_LDAP_SUCCESS) { (void) mutex_unlock(&info->mutex[0]); return (rc); } if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_serverInfo: " "%s is converted to %s\n", server->sinfo[0].addr, server->sinfo[0].hostname); } } ret_addr = server->sinfo[0].addr; ret_addrFQDN = server->sinfo[0].hostname; } else ret_addr = server->sinfo[0].addr; len = strlen(ret_addr) + strlen(server->sinfo[0].rootDSE_data) + strlen(DOORLINESEP) + 1; if (ret_addrFQDN != NULL) len += strlen(ret_addrFQDN) + strlen(DOORLINESEP); *output = (char *)malloc(len); if (*output == NULL) { (void) mutex_unlock(&info->mutex[0]); return (NS_LDAP_MEMORY); } if (ret_addrFQDN == NULL) (void) snprintf(*output, len, "%s%s%s", ret_addr, DOORLINESEP, server->sinfo[0].rootDSE_data); else (void) snprintf(*output, len, "%s%s%s%s%s", ret_addr, DOORLINESEP, ret_addrFQDN, DOORLINESEP, server->sinfo[0].rootDSE_data); server->sinfo[0].info_status = INFO_STATUS_OLD; (void) mutex_unlock(&info->mutex[0]); return (NS_LDAP_SUCCESS); } else return (-99); } /* * Format previous and next refresh time */ static int getldap_format_refresh_time(char **output, time_t *prev, time_t *next) { #define TIME_FORMAT "%Y/%m/%d %H:%M:%S" #define TIME_HEADER1 " Previous refresh time: " #define TIME_HEADER2 " Next refresh time: " int hdr1_len = strlen(gettext(TIME_HEADER1)); int hdr2_len = strlen(gettext(TIME_HEADER2)); struct tm tm; char nbuf[256]; char pbuf[256]; int len; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_format_refresh_time()...\n"); } *output = NULL; /* format the time of previous refresh */ if (*prev != 0) { (void) localtime_r(prev, &tm); (void) strftime(pbuf, sizeof (pbuf) - 1, TIME_FORMAT, &tm); } else { (void) strcpy(pbuf, gettext("NOT DONE")); } /* format the time of next refresh */ if (*next != 0) { (void) localtime_r(next, &tm); (void) strftime(nbuf, sizeof (nbuf) - 1, TIME_FORMAT, &tm); } else { (void) strcpy(nbuf, gettext("NOT SET")); } len = hdr1_len + hdr2_len + strlen(nbuf) + strlen(pbuf) + 2 * strlen(DOORLINESEP) + 1; *output = malloc(len); if (*output == NULL) return (-1); (void) snprintf(*output, len, "%s%s%s%s%s%s", gettext(TIME_HEADER1), pbuf, DOORLINESEP, gettext(TIME_HEADER2), nbuf, DOORLINESEP); return (NS_LDAP_SUCCESS); } /* * getldap_get_server_stat processes the GETSTAT request passed * to this function from getldap_serverInfo_op(). * output: * a buffer containing info for all the servers. * For each server, the data is in the following format: * server: server address or name, status: unknown|up|down|removed DOORLINESEP * for example: ( here | used as DOORLINESEP for visual purposes) * server: 1.2.3.4, status: down|server: 2.2.2.2, status: up| * NOTE: caller should free this buffer when done using it */ static int getldap_get_server_stat(server_info_t *head, char **output, time_t *prev, time_t *next) { #define S_HEADER "Server information: " #define S_FORMAT " server: %s, status: %s%s" #define S_ERROR " error message: %s%s" server_info_t *info = NULL; int header_len = strlen(gettext(S_HEADER)); int format_len = strlen(gettext(S_FORMAT)); int error_len = strlen(gettext(S_ERROR)); int len = header_len + strlen(DOORLINESEP); int len1 = 0; char *status, *output1 = NULL, *tmpptr; *output = NULL; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_server_stat()...\n"); } if (head == NULL) { logit("getldap_get_server_stat: " "invalid serverInfo list.\n"); return (-1); } /* format previous and next refresh time */ (void) getldap_format_refresh_time(&output1, prev, next); if (output1 == NULL) return (-1); len += strlen(output1); len1 = len + strlen(DOORLINESEP) + 1; *output = (char *)calloc(1, len1); if (*output == NULL) { free(output1); return (-1); } /* insert header string and refresh time info */ (void) snprintf(*output, len1, "%s%s%s", gettext(S_HEADER), DOORLINESEP, output1); for (info = head; info; info = info->next) { /* * make sure the server info stays the same * while the data is being processed */ (void) mutex_lock(&info->mutex[1]); /* * When the updating process is under way(getldap_get_rootDSE) * the update copy(sinfo[1] is the latest copy. * When the updating process * is done, the current copy (sinfo[0]) has the latest status, * which is still identical to the update copy. * So update copy has the latest status. * Use the update copy(sinfo[1]) to show status * (ldap_cachemgr -g). * */ switch (info->sinfo[1].server_status) { case INFO_SERVER_UNKNOWN: status = gettext("UNKNOWN"); break; case INFO_SERVER_CONNECTING: status = gettext("CONNECTING"); break; case INFO_SERVER_UP: status = gettext("UP"); break; case INFO_SERVER_ERROR: status = gettext("ERROR"); break; case INFO_SERVER_REMOVED: status = gettext("REMOVED"); break; } len += format_len + strlen(status) + strlen(info->sinfo[1].addr) + strlen(DOORLINESEP); if (info->sinfo[1].errormsg != NULL) len += error_len + strlen(info->sinfo[1].errormsg) + strlen(DOORLINESEP); tmpptr = (char *)realloc(*output, len); if (tmpptr == NULL) { free(output1); free(*output); *output = NULL; (void) mutex_unlock(&info->mutex[1]); return (-1); } else *output = tmpptr; /* insert server IP addr or name and status */ len1 = len - strlen(*output); (void) snprintf(*output + strlen(*output), len1, gettext(S_FORMAT), info->sinfo[1].addr, status, DOORLINESEP); /* insert error message if any */ len1 = len - strlen(*output); if (info->sinfo[1].errormsg != NULL) (void) snprintf(*output + strlen(*output), len1, gettext(S_ERROR), info->sinfo[1].errormsg, DOORLINESEP); (void) mutex_unlock(&info->mutex[1]); } free(output1); return (NS_LDAP_SUCCESS); } /* * Format and return the refresh time statistics */ static int getldap_get_refresh_stat(char **output) { #define R_HEADER0 "Configuration refresh information: " #define R_HEADER1 " Configured to NO REFRESH." int hdr0_len = strlen(gettext(R_HEADER0)); int hdr1_len = strlen(gettext(R_HEADER1)); int cache_ttl = -1, len = 0; time_t expire = 0; void **paramVal = NULL; ns_ldap_error_t *errorp = NULL; char *output1 = NULL; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_refresh_stat()...\n"); } *output = NULL; /* get configured cache TTL */ if ((__ns_ldap_getParam(NS_LDAP_CACHETTL_P, ¶mVal, &errorp) == NS_LDAP_SUCCESS) && paramVal != NULL && (char *)*paramVal != NULL) { cache_ttl = atol((char *)*paramVal); } else { if (errorp) __ns_ldap_freeError(&errorp); } (void) __ns_ldap_freeParam(¶mVal); /* cound not get cache TTL */ if (cache_ttl == -1) return (-1); if (cache_ttl == 0) { len = hdr0_len + hdr1_len + 2 * strlen(DOORLINESEP) + 1; *output = malloc(len); if (*output == NULL) return (-1); (void) snprintf(*output, len, "%s%s%s%s", gettext(R_HEADER0), DOORLINESEP, gettext(R_HEADER1), DOORLINESEP); } else { /* get configuration expiration time */ if ((__ns_ldap_getParam(NS_LDAP_EXP_P, ¶mVal, &errorp) == NS_LDAP_SUCCESS) && paramVal != NULL && (char *)*paramVal != NULL) { expire = (time_t)atol((char *)*paramVal); } else { if (errorp) __ns_ldap_freeError(&errorp); } (void) __ns_ldap_freeParam(¶mVal); /* cound not get expiration time */ if (expire == -1) return (-1); /* format previous and next refresh time */ (void) getldap_format_refresh_time(&output1, &prev_refresh_time, &expire); if (output1 == NULL) return (-1); len = hdr0_len + strlen(output1) + 2 * strlen(DOORLINESEP) + 1; *output = malloc(len); if (*output == NULL) { free(output1); return (-1); } (void) snprintf(*output, len, "%s%s%s%s", gettext(R_HEADER0), DOORLINESEP, output1, DOORLINESEP); free(output1); } return (NS_LDAP_SUCCESS); } static int getldap_get_cacheTTL() { void **paramVal = NULL; ns_ldap_error_t *error; int rc = 0, cachettl; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_cacheTTL()....\n"); } if ((rc = __ns_ldap_getParam(NS_LDAP_CACHETTL_P, ¶mVal, &error)) != NS_LDAP_SUCCESS) { if (error != NULL && error->message != NULL) logit("Error: Unable to get configuration " "refresh TTL: %s\n", error->message); else { char *tmp; __ns_ldap_err2str(rc, &tmp); logit("Error: Unable to get configuration " "refresh TTL: %s\n", tmp); } (void) __ns_ldap_freeParam(¶mVal); (void) __ns_ldap_freeError(&error); return (-1); } if (paramVal == NULL || (char *)*paramVal == NULL) return (-1); cachettl = atol((char *)*paramVal); (void) __ns_ldap_freeParam(¶mVal); return (cachettl); } /* * This function implements the adaptive server list refresh * algorithm used by ldap_cachemgr. The idea is to have the * refresh TTL adjust itself between maximum and minimum * values. If the server list has been walked three times * in a row without errors, the TTL will be doubled. This will * be done repeatedly until the maximum value is reached * or passed. If passed, the maximum value will be used. * If any time a server is found to be down/bad, either * after another server list walk or informed by libsldap via * the GETLDAPSERVER door calls, the TTL will be set to half * of its value, again repeatedly, but no less than the minimum * value. Also, at any time, if all the servers on the list * are found to be down/bad, the TTL will be set to minimum, * so that a "no-server" refresh loop should be entered to try * to find a good server as soon as possible. The caller * could check the no_gd_server flag for this situation. * The maximum and minimum values are initialized when the input * refresh_ttl is set to zero, this should occur during * ldap_cachemgr startup or every time the server list is * recreated after the configuration profile is refreshed * from an LDAP server. The maximum is set to the value of * the NS_LDAP_CACHETTL parameter (configuration profile * refresh TTL), but if it is zero (never refreshed) or can * not be retrieved, the maximum is set to the macro * REFRESHTTL_MAX (12 hours) defined below. The minimum is * set to REFRESHTTL_MIN, which is the TCP connection timeout * (tcptimeout) set via the LDAP API ldap_set_option() * with the new LDAP_X_OPT_CONNECT_TIMEOUT option plus 10 seconds. * This accounts for the maximum possible timeout value for an * LDAP TCP connect call.The first refresh TTL, initial value of * refresh_ttl, will be set to the smaller of the two, * REFRESHTTL_REGULAR (10 minutes) or (REFRESHTTL_MAX + REFRESHTTL_MIN)/2. * The idea is to have a low starting value and have the value * stay low if the network/server is unstable, but eventually * the value will move up to maximum and stay there if the * network/server is stable. */ static int getldap_set_refresh_ttl(server_info_t *head, int *refresh_ttl, int *no_gd_server) { #define REFRESHTTL_REGULAR 600 #define REFRESHTTL_MAX 43200 /* tcptimeout is in milliseconds */ #define REFRESHTTL_MIN (tcptimeout/1000) + 10 #define UP_REFRESH_TTL_NUM 2 static mutex_t refresh_mutex; static int refresh_ttl_max = 0; static int refresh_ttl_min = 0; static int num_walked_ok = 0; int num_servers = 0; int num_good_servers = 0; int num_prev_good_servers = 0; server_info_t *info; /* allow one thread at a time */ (void) mutex_lock(&refresh_mutex); if (current_admin.debug_level >= DBG_ALL) { logit("getldap_set_refresh_ttl()...\n"); } if (!head || !refresh_ttl || !no_gd_server) { logit("getldap_set_refresh_ttl: head is " "NULL or refresh_ttl is NULL or " "no_gd_server is NULL"); (void) mutex_unlock(&refresh_mutex); return (-1); } *no_gd_server = FALSE; /* * init max. min. TTLs if first time through or a fresh one */ if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("getldap_set_refresh_ttl:(1) refresh ttl is %d " "seconds\n", *refresh_ttl); } if (*refresh_ttl == 0) { num_walked_ok = 0; /* * init cache manager server list TTL: * * init the min. TTL to * REFRESHTTL_MIN ( 2*(TCP MSL) + 10 seconds) */ refresh_ttl_min = REFRESHTTL_MIN; /* * try to set the max. TTL to * configuration refresh TTL (NS_LDAP_CACHETTL), * if error (-1), or never refreshed (0), * set it to REFRESHTTL_MAX (12 hours) */ refresh_ttl_max = getldap_get_cacheTTL(); if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("getldap_set_refresh_ttl:(2) refresh ttl is %d " "seconds\n", *refresh_ttl); logit("getldap_set_refresh_ttl:(2) max ttl is %d, " "min ttl is %d seconds\n", refresh_ttl_max, refresh_ttl_min); } if (refresh_ttl_max <= 0) refresh_ttl_max = REFRESHTTL_MAX; else if (refresh_ttl_max < refresh_ttl_min) refresh_ttl_max = refresh_ttl_min; /* * init the first TTL to the smaller of the two: * REFRESHTTL_REGULAR ( 10 minutes), * (refresh_ttl_max + refresh_ttl_min)/2 */ *refresh_ttl = REFRESHTTL_REGULAR; if (*refresh_ttl > (refresh_ttl_max + refresh_ttl_min) / 2) *refresh_ttl = (refresh_ttl_max + refresh_ttl_min) / 2; if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("getldap_set_refresh_ttl:(3) refresh ttl is %d " "seconds\n", *refresh_ttl); logit("getldap_set_refresh_ttl:(3) max ttl is %d, " "min ttl is %d seconds\n", refresh_ttl_max, refresh_ttl_min); } } /* * get the servers statistics: * number of servers on list * number of good servers on list * number of pevious good servers on list */ for (info = head; info; info = info->next) { num_servers++; (void) mutex_lock(&info->mutex[0]); if (info->sinfo[0].server_status == INFO_SERVER_UP) num_good_servers++; /* * Server's previous status could be UNKNOWN * only between the very first and second * refresh. Treat that UNKNOWN status as up */ if (info->sinfo[0].prev_server_status == INFO_SERVER_UP || info->sinfo[0].prev_server_status == INFO_SERVER_UNKNOWN) num_prev_good_servers++; (void) mutex_unlock(&info->mutex[0]); } /* * if the server list is walked three times in a row * without problems, double the refresh TTL but no more * than the max. refresh TTL */ if (num_good_servers == num_servers) { num_walked_ok++; if (num_walked_ok > UP_REFRESH_TTL_NUM) { *refresh_ttl = *refresh_ttl * 2; if (*refresh_ttl > refresh_ttl_max) *refresh_ttl = refresh_ttl_max; num_walked_ok = 0; } if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("getldap_set_refresh_ttl:(4) refresh ttl is %d " "seconds\n", *refresh_ttl); } } else if (num_good_servers == 0) { /* * if no good server found, * set refresh TTL to miminum */ *refresh_ttl = refresh_ttl_min; *no_gd_server = TRUE; num_walked_ok = 0; if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("getldap_set_refresh_ttl:(5) refresh ttl is %d " "seconds\n", *refresh_ttl); } } else if (num_prev_good_servers > num_good_servers) { /* * if more down/bad servers found, * decrease the refresh TTL by half * but no less than the min. refresh TTL */ *refresh_ttl = *refresh_ttl / 2; if (*refresh_ttl < refresh_ttl_min) *refresh_ttl = refresh_ttl_min; num_walked_ok = 0; logit("getldap_set_refresh_ttl:(6) refresh ttl is %d " "seconds\n", *refresh_ttl); } if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("getldap_set_refresh_ttl:(7) refresh ttl is %d seconds\n", *refresh_ttl); } (void) mutex_unlock(&refresh_mutex); return (0); } static int getldap_serverInfo_op(info_op_t op, char *input, char **output) { static rwlock_t info_lock = DEFAULTRWLOCK; static rwlock_t info_lock_old = DEFAULTRWLOCK; static mutex_t info_mutex; static cond_t info_cond; static int creating = FALSE; static int refresh_ttl = 0; static int sec_to_refresh = 0; static int in_no_server_mode = FALSE; static server_info_t *serverInfo = NULL; static server_info_t *serverInfo_old = NULL; server_info_t *serverInfo_1; int is_creating; int err, no_server_good = FALSE; int server_removed = FALSE; int fall_thru = FALSE; static struct timespec timeout; struct timespec new_timeout; struct timeval tp; static time_t prev_refresh = 0, next_refresh = 0; ns_server_status_t changed = 0; (void) pthread_setname_np(pthread_self(), "getldap_serverinfo"); if (current_admin.debug_level >= DBG_ALL) { logit("getldap_serverInfo_op()...\n"); } switch (op) { case INFO_OP_CREATE: if (current_admin.debug_level >= DBG_ALL) { logit("operation is INFO_OP_CREATE...\n"); } /* * indicate that the server info is being * (re)created, so that the refresh thread * will not refresh the info list right * after the list got (re)created */ (void) mutex_lock(&info_mutex); is_creating = creating; creating = TRUE; (void) mutex_unlock(&info_mutex); if (is_creating) break; /* * create an empty info list */ (void) getldap_init_serverInfo(&serverInfo_1); /* * exit if list not created */ if (serverInfo_1 == NULL) { (void) mutex_lock(&info_mutex); creating = FALSE; (void) mutex_unlock(&info_mutex); break; } /* * make the new server info available: * use writer lock here, so that the switch * is done after all the reader locks have * been released. */ (void) rw_wrlock(&info_lock); serverInfo = serverInfo_1; /* * if this is the first time * the server list is being created, * (i.e., serverInfo_old is NULL) * make the old list same as the new * so the GETSERVER code can do its work */ if (serverInfo_old == NULL) serverInfo_old = serverInfo_1; (void) rw_unlock(&info_lock); /* * fill the new info list */ (void) rw_rdlock(&info_lock); /* reset bind time (tcptimeout) */ (void) getldap_set_serverInfo(serverInfo, 1, INFO_OP_CREATE); (void) mutex_lock(&info_mutex); /* * set cache manager server list TTL, * set refresh_ttl to zero to indicate a fresh one */ refresh_ttl = 0; (void) getldap_set_refresh_ttl(serverInfo, &refresh_ttl, &no_server_good); sec_to_refresh = refresh_ttl; /* statistics: previous refresh time */ if (gettimeofday(&tp, NULL) == 0) prev_refresh = tp.tv_sec; creating = FALSE; /* * if no server found or available, * tell the server info refresh thread * to start the "no-server" refresh loop * otherwise reset the in_no_server_mode flag */ if (no_server_good) { sec_to_refresh = 0; in_no_server_mode = TRUE; } else in_no_server_mode = FALSE; /* * awake the sleeping refresh thread */ (void) cond_signal(&info_cond); (void) mutex_unlock(&info_mutex); (void) rw_unlock(&info_lock); /* * delete the old server info */ (void) rw_wrlock(&info_lock_old); if (serverInfo_old != serverInfo) (void) getldap_destroy_serverInfo(serverInfo_old); /* * serverInfo_old needs to be the same as * serverinfo now. * it will be used by GETSERVER processing. */ serverInfo_old = serverInfo; (void) rw_unlock(&info_lock_old); break; case INFO_OP_DELETE: if (current_admin.debug_level >= DBG_ALL) { logit("operation is INFO_OP_DELETE...\n"); } /* * use writer lock here, so that the delete would * not start until all the reader locks have * been released. */ (void) rw_wrlock(&info_lock); if (serverInfo) (void) getldap_destroy_serverInfo(serverInfo); serverInfo = NULL; (void) rw_unlock(&info_lock); break; case INFO_OP_REFRESH: if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("operation is INFO_OP_REFRESH...\n"); } /* * if server info is currently being * (re)created, do nothing */ (void) mutex_lock(&info_mutex); is_creating = creating; (void) mutex_unlock(&info_mutex); if (is_creating) break; (void) rw_rdlock(&info_lock); if (serverInfo) { /* do not reset bind time (tcptimeout) */ (void) getldap_set_serverInfo(serverInfo, 0, INFO_OP_REFRESH); (void) mutex_lock(&info_mutex); /* statistics: previous refresh time */ if (gettimeofday(&tp, NULL) == 0) prev_refresh = tp.tv_sec; /* * set cache manager server list TTL */ (void) getldap_set_refresh_ttl(serverInfo, &refresh_ttl, &no_server_good); /* * if no good server found, * tell the server info refresh thread * to start the "no-server" refresh loop * otherwise reset the in_no_server_mode flag */ if (no_server_good) { in_no_server_mode = TRUE; sec_to_refresh = 0; } else { in_no_server_mode = FALSE; sec_to_refresh = refresh_ttl; } if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("getldap_serverInfo_op(" "INFO_OP_REFRESH):" " seconds refresh: %d second(s)....\n", sec_to_refresh); } (void) mutex_unlock(&info_mutex); } (void) rw_unlock(&info_lock); break; case INFO_OP_REFRESH_WAIT: if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("operation is INFO_OP_REFRESH_WAIT...\n"); } (void) cond_init(&info_cond, USYNC_THREAD, NULL); (void) mutex_lock(&info_mutex); err = 0; while (err != ETIME) { int sleeptime; /* * if need to go into the "no-server" refresh * loop, set timout value to * REFRESH_DELAY_WHEN_NO_SERVER */ if (sec_to_refresh == 0) { sec_to_refresh = refresh_ttl; timeout.tv_sec = time(NULL) + REFRESH_DELAY_WHEN_NO_SERVER; sleeptime = REFRESH_DELAY_WHEN_NO_SERVER; if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("getldap_serverInfo_op(" "INFO_OP_REFRESH_WAIT):" " entering no-server " "refresh loop...\n"); } } else { timeout.tv_sec = time(NULL) + sec_to_refresh; sleeptime = sec_to_refresh; } timeout.tv_nsec = 0; /* statistics: next refresh time */ next_refresh = timeout.tv_sec; if (current_admin.debug_level >= DBG_SERVER_LIST_REFRESH) { logit("getldap_serverInfo_op(" "INFO_OP_REFRESH_WAIT):" " about to sleep for %d second(s)...\n", sleeptime); } err = cond_timedwait(&info_cond, &info_mutex, &timeout); } (void) cond_destroy(&info_cond); (void) mutex_unlock(&info_mutex); break; case INFO_OP_GETSERVER: if (current_admin.debug_level >= DBG_ALL) { logit("operation is INFO_OP_GETSERVER...\n"); } *output = NULL; /* * GETSERVER processing always use * serverInfo_old to retrieve server infomation. * serverInfo_old is equal to serverInfo * most of the time, except when a new * server list is being created. * This is why the check for is_creating * is needed below. */ (void) rw_rdlock(&info_lock_old); if (serverInfo_old == NULL) { (void) rw_unlock(&info_lock_old); break; } else (void) getldap_get_serverInfo(serverInfo_old, input, output, &server_removed); (void) rw_unlock(&info_lock_old); /* * Return here and let remove server thread do its job in * another thread. It executes INFO_OP_REMOVESERVER code later. */ if (server_removed) break; fall_thru = TRUE; /* FALL THROUGH */ case INFO_OP_REMOVESERVER: /* * INFO_OP_GETSERVER and INFO_OP_REMOVESERVER share the * following code except (!fall thru) part. */ /* * if server info is currently being * (re)created, do nothing */ (void) mutex_lock(&info_mutex); is_creating = creating; (void) mutex_unlock(&info_mutex); if (is_creating) break; if (!fall_thru) { if (current_admin.debug_level >= DBG_ALL) logit("operation is INFO_OP_REMOVESERVER...\n"); (void) rw_rdlock(&info_lock_old); changed = set_server_status(input, serverInfo_old); (void) rw_unlock(&info_lock_old); if (changed) create_buf_and_notify(input, changed); else break; } /* * set cache manager server list TTL if necessary */ if (*output == NULL || changed) { (void) rw_rdlock(&info_lock); (void) mutex_lock(&info_mutex); (void) getldap_set_refresh_ttl(serverInfo, &refresh_ttl, &no_server_good); /* * if no good server found, need to go into * the "no-server" refresh loop * to find a server as soon as possible * otherwise reset the in_no_server_mode flag */ if (no_server_good) { /* * if already in no-server mode, * don't brother */ if (in_no_server_mode == FALSE) { sec_to_refresh = 0; in_no_server_mode = TRUE; (void) cond_signal(&info_cond); } (void) mutex_unlock(&info_mutex); (void) rw_unlock(&info_lock); break; } else { in_no_server_mode = FALSE; sec_to_refresh = refresh_ttl; } /* * if the refresh thread will be timed out * longer than refresh_ttl seconds, * wake it up to make it wait on the new * time out value */ new_timeout.tv_sec = time(NULL) + refresh_ttl; if (new_timeout.tv_sec < timeout.tv_sec) (void) cond_signal(&info_cond); (void) mutex_unlock(&info_mutex); (void) rw_unlock(&info_lock); } break; case INFO_OP_GETSTAT: if (current_admin.debug_level >= DBG_ALL) { logit("operation is INFO_OP_GETSTAT...\n"); } *output = NULL; (void) rw_rdlock(&info_lock); if (serverInfo) { (void) getldap_get_server_stat(serverInfo, output, &prev_refresh, &next_refresh); } (void) rw_unlock(&info_lock); break; default: logit("getldap_serverInfo_op(): " "invalid operation code (%d).\n", op); return (-1); break; } return (NS_LDAP_SUCCESS); } void getldap_serverInfo_refresh() { int always = 1; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_serverInfo_refresh()...\n"); } /* create the server info list */ (void) getldap_serverInfo_op(INFO_OP_CREATE, NULL, NULL); while (always) { /* * the operation INFO_OP_REFRESH_WAIT * causes this thread to wait until * it is time to do refresh, * see getldap_serverInfo_op() for details */ (void) getldap_serverInfo_op(INFO_OP_REFRESH_WAIT, NULL, NULL); (void) getldap_serverInfo_op(INFO_OP_REFRESH, NULL, NULL); } } void getldap_getserver(LineBuf *config_info, ldap_call_t *in) { char req[] = "0"; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_getserver()...\n"); } config_info->len = 0; /* make sure the request is valid */ req[0] = (in->ldap_u.servername)[0]; if ((req[0] != '\0') && (strcmp(req, NS_CACHE_NEW) != 0) && (strcmp(req, NS_CACHE_NORESP) != 0) && (strcmp(req, NS_CACHE_NEXT) != 0) && (strcmp(req, NS_CACHE_WRITE) != 0)) { return; } (void) getldap_serverInfo_op(INFO_OP_GETSERVER, in->ldap_u.domainname, &config_info->str); if (config_info->str == NULL) return; config_info->len = strlen(config_info->str) + 1; if (current_admin.debug_level >= DBG_PROFILE_REFRESH) { /* Log server IP */ char *ptr, separator; ptr = strstr(config_info->str, DOORLINESEP); if (ptr) { separator = *ptr; *ptr = '\0'; logit("getldap_getserver: got server %s\n", config_info->str); *ptr = separator; } else logit("getldap_getserver: Missing %s." " Internal error\n", DOORLINESEP); } } void getldap_get_cacheData(LineBuf *config_info, ldap_call_t *in) { char *instr = NULL; int datatype = CACHE_MAP_UNKNOWN; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_cacheData()...\n"); } config_info->len = 0; config_info->str = NULL; /* make sure the request is valid */ if (strncmp(in->ldap_u.servername, NS_CACHE_DN2DOMAIN, strlen(NS_CACHE_DN2DOMAIN)) == 0) datatype = CACHE_MAP_DN2DOMAIN; if (datatype == CACHE_MAP_UNKNOWN) return; instr = strstr(in->ldap_u.servername, DOORLINESEP); if (instr == NULL) return; instr += strlen(DOORLINESEP); if (*instr == '\0') return; (void) getldap_cache_op(CACHE_OP_FIND, datatype, instr, &config_info->str); if (config_info->str != NULL) { config_info->len = strlen(config_info->str) + 1; } } int getldap_set_cacheData(ldap_call_t *in) { char *instr1 = NULL; char *instr2 = NULL; int datatype = CACHE_MAP_UNKNOWN; int rc = 0; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_set_cacheData()...\n"); } /* make sure the request is valid */ if (strncmp(in->ldap_u.servername, NS_CACHE_DN2DOMAIN, strlen(NS_CACHE_DN2DOMAIN)) == 0) datatype = CACHE_MAP_DN2DOMAIN; if (datatype == CACHE_MAP_UNKNOWN) return (-1); instr1 = strstr(in->ldap_u.servername, DOORLINESEP); if (instr1 == NULL) return (-1); *instr1 = '\0'; instr1 += strlen(DOORLINESEP); if (*instr1 == '\0') return (-1); instr2 = strstr(instr1, DOORLINESEP); if (instr2 == NULL) return (-1); *instr2 = '\0'; instr2 += strlen(DOORLINESEP); if (*instr2 == '\0') return (-1); rc = getldap_cache_op(CACHE_OP_ADD, datatype, instr1, &instr2); if (rc != NS_LDAP_SUCCESS) return (-1); return (0); } void getldap_get_cacheStat(LineBuf *stat_info) { char *foutstr = NULL; char *soutstr = NULL; char *coutstr = NULL; int infoSize; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_cacheStat()...\n"); } stat_info->str = NULL; stat_info->len = 0; /* get refersh statisitcs */ (void) getldap_get_refresh_stat(&foutstr); if (foutstr == NULL) return; /* get server statisitcs */ (void) getldap_serverInfo_op(INFO_OP_GETSTAT, NULL, &soutstr); if (soutstr == NULL) { free(foutstr); return; } /* get cache data statisitcs */ (void) getldap_cache_op(CACHE_OP_GETSTAT, 0, NULL, &coutstr); if (coutstr == NULL) { free(foutstr); free(soutstr); return; } infoSize = strlen(foutstr) + strlen(soutstr) + strlen(coutstr) + 3; stat_info->str = calloc(infoSize, sizeof (char)); if (stat_info->str != NULL) { (void) strncpy(stat_info->str, foutstr, strlen(foutstr) + 1); (void) strncat(stat_info->str, soutstr, strlen(soutstr) + 1); (void) strncat(stat_info->str, coutstr, strlen(coutstr) + 1); stat_info->len = infoSize; } free(foutstr); free(soutstr); free(coutstr); } static int checkupdate(int sighup) { int value; (void) rw_wrlock(&ldap_lock); value = sighup; (void) rw_unlock(&ldap_lock); return (value == TRUE); } static int update_from_profile(int *change_status) { ns_ldap_result_t *result = NULL; char searchfilter[BUFSIZ]; ns_ldap_error_t *error; int rc; void **paramVal = NULL; ns_config_t *ptr = NULL; char *profile = NULL; char errstr[MAXERROR]; if (current_admin.debug_level >= DBG_ALL) { logit("update_from_profile....\n"); } do { (void) rw_wrlock(&ldap_lock); sighup_update = FALSE; (void) rw_unlock(&ldap_lock); if ((rc = __ns_ldap_getParam(NS_LDAP_PROFILE_P, ¶mVal, &error)) != NS_LDAP_SUCCESS) { if (error != NULL && error->message != NULL) logit("Error: Unable to profile name: %s\n", error->message); else { char *tmp; __ns_ldap_err2str(rc, &tmp); logit("Error: Unable to profile name: %s\n", tmp); } (void) __ns_ldap_freeParam(¶mVal); (void) __ns_ldap_freeError(&error); return (-1); } if (paramVal && *paramVal) profile = strdup((char *)*paramVal); (void) __ns_ldap_freeParam(¶mVal); if (profile == NULL) { return (-1); } (void) snprintf(searchfilter, BUFSIZ, _PROFILE_FILTER, _PROFILE1_OBJECTCLASS, _PROFILE2_OBJECTCLASS, profile); if ((rc = __ns_ldap_list(_PROFILE_CONTAINER, (const char *)searchfilter, NULL, NULL, NULL, 0, &result, &error, NULL, NULL)) != NS_LDAP_SUCCESS) { /* * Is profile name the DEFAULTCONFIGNAME? * syslog Warning, otherwise syslog error. */ if (strcmp(profile, DEFAULTCONFIGNAME) == 0) { syslog(LOG_WARNING, "Ignoring attempt to refresh nonexistent " "default profile: %s.\n", profile); logit("Ignoring attempt to refresh nonexistent " "default profile: %s.\n", profile); } else if ((error != NULL) && (error->message != NULL)) { syslog(LOG_ERR, "Error: Unable to refresh profile:%s:" " %s\n", profile, error->message); logit("Error: Unable to refresh profile:" "%s:%s\n", profile, error->message); } else { syslog(LOG_ERR, "Error: Unable to refresh " "from profile:%s. (error=%d)\n", profile, rc); logit("Error: Unable to refresh from profile " "%s (error=%d)\n", profile, rc); } (void) __ns_ldap_freeError(&error); (void) __ns_ldap_freeResult(&result); free(profile); return (-1); } free(profile); } while (checkupdate(sighup_update) == TRUE); (void) rw_wrlock(&ldap_lock); ptr = __ns_ldap_make_config(result); (void) __ns_ldap_freeResult(&result); if (ptr == NULL) { logit("Error: __ns_ldap_make_config failed.\n"); (void) rw_unlock(&ldap_lock); return (-1); } /* * cross check the config parameters */ if (__s_api_crosscheck(ptr, errstr, B_TRUE) == NS_SUCCESS) { /* * reset the local profile TTL */ if (ptr->paramList[NS_LDAP_CACHETTL_P].ns_pc) current_admin.ldap_stat.ldap_ttl = atol(ptr->paramList[NS_LDAP_CACHETTL_P].ns_pc); if (current_admin.debug_level >= DBG_PROFILE_REFRESH) { logit("update_from_profile: reset profile TTL to %d" " seconds\n", current_admin.ldap_stat.ldap_ttl); logit("update_from_profile: expire time %ld " "seconds\n", ptr->paramList[NS_LDAP_EXP_P].ns_tm); } /* set ptr as current_config if the config is changed */ chg_test_config_change(ptr, change_status); rc = 0; } else { __s_api_destroy_config(ptr); logit("Error: downloaded profile failed to pass " "crosscheck (%s).\n", errstr); syslog(LOG_ERR, "ldap_cachemgr: %s", errstr); rc = -1; } (void) rw_unlock(&ldap_lock); return (rc); } int getldap_init() { ns_ldap_error_t *error; struct timeval tp; ldap_get_chg_cookie_t cookie; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_init()...\n"); } (void) __ns_ldap_setServer(TRUE); (void) rw_wrlock(&ldap_lock); if ((error = __ns_ldap_LoadConfiguration()) != NULL) { logit("Error: Unable to read '%s': %s\n", NSCONFIGFILE, error->message); (void) fprintf(stderr, gettext("\nError: Unable to read '%s': %s\n"), NSCONFIGFILE, error->message); __ns_ldap_freeError(&error); (void) rw_unlock(&ldap_lock); return (-1); } (void) rw_unlock(&ldap_lock); if (gettimeofday(&tp, NULL) == 0) { /* statistics: previous refresh time */ prev_refresh_time = tp.tv_sec; } /* initialize the data cache */ (void) getldap_cache_op(CACHE_OP_CREATE, 0, NULL, NULL); cookie.mgr_pid = getpid(); cookie.seq_num = 0; chg_config_cookie_set(&cookie); return (0); } static void perform_update(void) { ns_ldap_error_t *error = NULL; struct timeval tp; char buf[20]; int rc, rc1; int changed = 0; void **paramVal = NULL; ns_ldap_self_gssapi_config_t config; if (current_admin.debug_level >= DBG_ALL) { logit("perform_update()...\n"); } (void) __ns_ldap_setServer(TRUE); if (gettimeofday(&tp, NULL) != 0) return; rc = __ns_ldap_getParam(NS_LDAP_CACHETTL_P, ¶mVal, &error); if (rc == NS_LDAP_SUCCESS && paramVal != NULL) { current_admin.ldap_stat.ldap_ttl = atol((char *)*paramVal); } if (error != NULL) (void) __ns_ldap_freeError(&error); if (paramVal != NULL) (void) __ns_ldap_freeParam(¶mVal); if (current_admin.debug_level >= DBG_PROFILE_REFRESH) { logit("perform_update: current profile TTL is %d seconds\n", current_admin.ldap_stat.ldap_ttl); } if (current_admin.ldap_stat.ldap_ttl > 0) { /* * set the profile TTL parameter, just * in case that the downloading of * the profile from server would fail */ /* * NS_LDAP_EXP_P is a no op for __ns_ldap_setParam * It depends on NS_LDAP_CACHETTL_P to set it's value * Set NS_LDAP_CACHETTL_P here so NS_LDAP_EXP_P value * can be set. * NS_LDAP_CACHETTL_P value can be reset after the profile is * downloaded from the server, so is NS_LDAP_EXP_P. */ buf[19] = '\0'; /* null terminated the buffer */ if (__ns_ldap_setParam(NS_LDAP_CACHETTL_P, lltostr((long long)current_admin.ldap_stat.ldap_ttl, &buf[19]), &error) != NS_LDAP_SUCCESS) { logit("Error: __ns_ldap_setParam failed, status: %d " "message: %s\n", error->status, error->message); (void) __ns_ldap_freeError(&error); return; } (void) rw_wrlock(&ldap_lock); sighup_update = FALSE; (void) rw_unlock(&ldap_lock); do { rc = update_from_profile(&changed); if (rc != 0) { logit("Error: Unable to update from profile\n"); } } while (checkupdate(sighup_update) == TRUE); } else { rc = 0; } /* * recreate the server info list */ if (rc == 0) { (void) getldap_serverInfo_op(INFO_OP_CREATE, NULL, NULL); /* flush the data cache */ (void) getldap_cache_op(CACHE_OP_DELETE, 0, NULL, NULL); /* statistics: previous refresh time */ prev_refresh_time = tp.tv_sec; } rc1 = __ns_ldap_self_gssapi_config(&config); if (rc1 == NS_LDAP_SUCCESS) { if (config != NS_LDAP_SELF_GSSAPI_CONFIG_NONE) { rc1 = __ns_ldap_check_all_preq(0, 0, 0, config, &error); (void) __ns_ldap_freeError(&error); if (rc1 != NS_LDAP_SUCCESS) { logit("Error: Check on self credential " "prerquesites failed: %d\n", rc1); exit(rc1); } } } else { logit("Error: Failed to get self credential configuration %d\n", rc1); exit(rc1); } if (!changed) return; (void) rw_rdlock(&ldap_lock); if (((error = __ns_ldap_DumpConfiguration(NSCONFIGREFRESH)) != NULL) || ((error = __ns_ldap_DumpConfiguration(NSCREDREFRESH)) != NULL)) { logit("Error: __ns_ldap_DumpConfiguration failed, " "status: %d message: %s\n", error->status, error->message); __ns_ldap_freeError(&error); (void) rw_unlock(&ldap_lock); return; } if (rename(NSCONFIGREFRESH, NSCONFIGFILE) != 0) { logit("Error: unlink failed - errno: %s\n", strerror(errno)); syslog(LOG_ERR, "Unable to refresh profile, LDAP configuration" "files not written"); (void) rw_unlock(&ldap_lock); return; } if (rename(NSCREDREFRESH, NSCREDFILE) != 0) { /* * We probably have inconsistent configuration at this point. * If we were to create a backup file and rename it here, that * operation might also fail. Consequently there is no safe way * to roll back. */ logit("Error: unlink failed - errno: %s\n", strerror(errno)); syslog(LOG_ERR, "Unable to refresh profile consistently, " "LDAP configuration files inconsistent"); (void) rw_unlock(&ldap_lock); return; } (void) rw_unlock(&ldap_lock); } void getldap_refresh() { struct timespec timeout; int sleeptime; struct timeval tp; long expire = 0; void **paramVal = NULL; ns_ldap_error_t *errorp; int always = 1, err; int first_time = 1; int sig_done = 0; int dbg_level; (void) pthread_setname_np(pthread_self(), "getldap_refresh"); if (current_admin.debug_level >= DBG_ALL) { logit("getldap_refresh()...\n"); } /* * wait for an available server */ while (sig_done == 0) { (void) mutex_lock(&sig_mutex); sig_done = signal_done; (void) mutex_unlock(&sig_mutex); } (void) __ns_ldap_setServer(TRUE); while (always) { dbg_level = current_admin.debug_level; (void) rw_rdlock(&ldap_lock); sleeptime = current_admin.ldap_stat.ldap_ttl; if (dbg_level >= DBG_PROFILE_REFRESH) { logit("getldap_refresh: current profile TTL is %d " "seconds\n", current_admin.ldap_stat.ldap_ttl); } if (gettimeofday(&tp, NULL) == 0) { if ((__ns_ldap_getParam(NS_LDAP_EXP_P, ¶mVal, &errorp) == NS_LDAP_SUCCESS) && paramVal != NULL && (char *)*paramVal != NULL) { errno = 0; expire = atol((char *)*paramVal); (void) __ns_ldap_freeParam(¶mVal); if (errno == 0) { if (expire == 0) { first_time = 0; (void) rw_unlock(&ldap_lock); (void) cond_init(&cond, USYNC_THREAD, NULL); (void) mutex_lock(&sighuplock); timeout.tv_sec = CACHESLEEPTIME; timeout.tv_nsec = 0; if (dbg_level >= DBG_PROFILE_REFRESH) { logit("getldap_refresh:" "(1)about to sleep" " for %d seconds\n", CACHESLEEPTIME); } err = cond_reltimedwait(&cond, &sighuplock, &timeout); (void) cond_destroy(&cond); (void) mutex_unlock( &sighuplock); /* * if woke up by * getldap_revalidate(), * do update right away */ if (err == ETIME) continue; else { /* * if load * configuration failed * don't do update */ if (load_config()) perform_update (); continue; } } sleeptime = expire - tp.tv_sec; if (dbg_level >= DBG_PROFILE_REFRESH) { logit("getldap_refresh: expire " "time = %ld\n", expire); } } } } (void) rw_unlock(&ldap_lock); /* * if this is the first time downloading * the profile or expire time already passed, * do not wait, do update */ if (first_time == 0 && sleeptime > 0) { if (dbg_level >= DBG_PROFILE_REFRESH) { logit("getldap_refresh: (2)about to sleep " "for %d seconds\n", sleeptime); } (void) cond_init(&cond, USYNC_THREAD, NULL); (void) mutex_lock(&sighuplock); timeout.tv_sec = sleeptime; timeout.tv_nsec = 0; err = cond_reltimedwait(&cond, &sighuplock, &timeout); (void) cond_destroy(&cond); (void) mutex_unlock(&sighuplock); } /* * if load concfiguration failed * don't do update */ if (load_config()) perform_update(); first_time = 0; } } void getldap_revalidate() { if (current_admin.debug_level >= DBG_ALL) { logit("getldap_revalidate()...\n"); } /* block signal SIGHUP */ (void) sighold(SIGHUP); /* now awake the sleeping refresh thread */ (void) cond_signal(&cond); /* release signal SIGHUP */ (void) sigrelse(SIGHUP); } void getldap_admincred(LineBuf *config_info, ldap_call_t *in) { ns_ldap_error_t *error; ldap_config_out_t *cout; ucred_t *uc = NULL; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_admincred()...\n"); } /* check privileges */ if (is_root_or_all_privs("GETADMINCRED", &uc) == 0) { logit("admin credential requested by a non-root and no ALL " "privilege user not allowed"); config_info->str = NULL; config_info->len = 0; } else { (void) rw_rdlock(&ldap_lock); if ((error = __ns_ldap_LoadDoorInfo(config_info, in->ldap_u.domainname, NULL, 1)) != NULL) { if (error != NULL && error->message != NULL) logit("Error: ldap_lookup: %s\n", error->message); (void) __ns_ldap_freeError(&error); config_info->str = NULL; config_info->len = 0; } /* set change cookie */ cout = (ldap_config_out_t *)config_info->str; if (cout) cout->cookie = chg_config_cookie_get(); (void) rw_unlock(&ldap_lock); } } void getldap_lookup(LineBuf *config_info, ldap_call_t *in) { ns_ldap_error_t *error; ldap_config_out_t *cout; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_lookup()...\n"); } (void) rw_rdlock(&ldap_lock); if ((error = __ns_ldap_LoadDoorInfo(config_info, in->ldap_u.domainname, NULL, 0)) != NULL) { if (error != NULL && error->message != NULL) logit("Error: ldap_lookup: %s\n", error->message); (void) __ns_ldap_freeError(&error); config_info->str = NULL; config_info->len = 0; } /* set change cookie */ cout = (ldap_config_out_t *)config_info->str; if (cout) cout->cookie = chg_config_cookie_get(); (void) rw_unlock(&ldap_lock); } /* * It creates the header and data stream to be door returned and notify * chg_get_statusChange() threads. * This is called after all getldap_get_rootDSE() threads are joined. */ void test_server_change(server_info_t *head) { server_info_t *info; int len = 0, num = 0, ds_len = 0, new_len = 0, tlen = 0; char *tmp_buf = NULL, *ptr = NULL, *status = NULL; ldap_get_change_out_t *cout; ds_len = strlen(DOORLINESEP); for (info = head; info; info = info->next) { (void) mutex_lock(&info->mutex[0]); if (info->sinfo[0].change != 0) { /* "9.9.9.9|NS_SERVER_CHANGE_UP|" */ len += 2 * ds_len + strlen(info->sinfo[0].addr) + strlen(NS_SERVER_CHANGE_UP); num++; } (void) mutex_unlock(&info->mutex[0]); } if (len == 0) return; len++; /* '\0' */ tlen = sizeof (ldap_get_change_out_t) - sizeof (int) + len; if ((tmp_buf = malloc(tlen)) == NULL) return; cout = (ldap_get_change_out_t *)tmp_buf; cout->type = NS_STATUS_CHANGE_TYPE_SERVER; /* cout->cookie is set by chg_notify_statusChange */ cout->server_count = num; cout->data_size = len; /* Create IP|UP or DOWN|IP|UP or DOWN| ... */ ptr = cout->data; new_len = len; for (info = head; info; info = info->next) { (void) mutex_lock(&info->mutex[0]); if (info->sinfo[0].change == 0) { (void) mutex_unlock(&info->mutex[0]); continue; } if (info->sinfo[0].change == NS_SERVER_UP) status = NS_SERVER_CHANGE_UP; else if (info->sinfo[0].change == NS_SERVER_DOWN) status = NS_SERVER_CHANGE_DOWN; else { syslog(LOG_WARNING, gettext("Bad change value %d"), info->sinfo[0].change); (void) mutex_unlock(&info->mutex[0]); free(tmp_buf); return; } if ((snprintf(ptr, new_len, "%s%s%s%s", info->sinfo[0].addr, DOORLINESEP, status, DOORLINESEP)) >= new_len) { (void) mutex_unlock(&info->mutex[0]); break; } new_len -= strlen(ptr); ptr += strlen(ptr); (void) mutex_unlock(&info->mutex[0]); } (void) chg_notify_statusChange(tmp_buf); } /* * It creates the header and data stream to be door returned and notify * chg_get_statusChange() threads. * This is called in removing server case. */ static void create_buf_and_notify(char *input, ns_server_status_t st) { rm_svr_t *rms = (rm_svr_t *)input; char *tmp_buf, *ptr, *status; int len, tlen; ldap_get_change_out_t *cout; /* IP|UP or DOWN| */ len = 2 * strlen(DOORLINESEP) + strlen(rms->addr) + strlen(NS_SERVER_CHANGE_UP) + 1; tlen = sizeof (ldap_get_change_out_t) - sizeof (int) + len; if ((tmp_buf = malloc(tlen)) == NULL) return; cout = (ldap_get_change_out_t *)tmp_buf; cout->type = NS_STATUS_CHANGE_TYPE_SERVER; /* cout->cookie is set by chg_notify_statusChange */ cout->server_count = 1; cout->data_size = len; /* Create IP|DOWN| */ ptr = cout->data; if (st == NS_SERVER_UP) status = NS_SERVER_CHANGE_UP; else if (st == NS_SERVER_DOWN) status = NS_SERVER_CHANGE_DOWN; (void) snprintf(ptr, len, "%s%s%s%s", rms->addr, DOORLINESEP, status, DOORLINESEP); (void) chg_notify_statusChange(tmp_buf); } /* * Return: 0 server is down, 1 server is up */ static int contact_server(char *addr) { char *rootDSE = NULL; ns_ldap_error_t *error = NULL; int rc; if (__ns_ldap_getRootDSE(addr, &rootDSE, &error, SA_ALLOW_FALLBACK) != NS_LDAP_SUCCESS) { if (current_admin.debug_level >= DBG_ALL) logit("get rootDSE %s failed. %s", addr, error->message ? error->message : ""); rc = 0; } else rc = 1; if (rootDSE) free(rootDSE); if (error) (void) __ns_ldap_freeError(&error); return (rc); } /* * The thread is spawned to do contact_server() so it won't be blocking * getldap_serverInfo_op(INFO_OP_GETSERVER, ...) case. * After contact_server() is done, it calls * getldap_serverInfo_op(INFO_OP_REMOVESERVER, ...) to return to the remaining * program flow. It's meant to maintain the original program flow yet be * non-blocking when it's contacting server. */ static void * remove_server_thread(void *arg) { char *addr = (char *)arg, *out = NULL; int up; rm_svr_t rms; (void) pthread_setname_np(pthread_self(), "remove_server"); up = contact_server(addr); rms.addr = addr; rms.up = up; (void) getldap_serverInfo_op(INFO_OP_REMOVESERVER, (char *)&rms, &out); free(addr); thr_exit(NULL); return (NULL); } /* * addr is allocated and is freed by remove_server_thread * It starts a thread to contact server and remove server to avoid long wait * or recursion. */ static void remove_server(char *addr) { if (thr_create(NULL, 0, remove_server_thread, (void *)addr, THR_BOUND|THR_DETACHED, NULL) != 0) { free(addr); syslog(LOG_ERR, "thr_create failed for remove_server_thread"); } } /* * Compare the server_status and mark it up or down accordingly. * This is called in removing server case. */ static ns_server_status_t set_server_status(char *input, server_info_t *head) { rm_svr_t *rms = (rm_svr_t *)input; ns_server_status_t changed = 0; server_info_t *info; for (info = head; info != NULL; info = info->next) { (void) mutex_lock(&info->mutex[0]); if (strcmp(info->sinfo[0].addr, rms->addr) == 0) { if (info->sinfo[0].server_status == INFO_SERVER_UP && rms->up == FALSE) { info->sinfo[0].prev_server_status = info->sinfo[0].server_status; info->sinfo[0].server_status = INFO_SERVER_ERROR; info->sinfo[0].change = NS_SERVER_DOWN; changed = NS_SERVER_DOWN; } else if (info->sinfo[0].server_status == INFO_SERVER_ERROR && rms->up == TRUE) { /* * It should be INFO_SERVER_UP, but check here */ info->sinfo[0].prev_server_status = info->sinfo[0].server_status; info->sinfo[0].server_status = INFO_SERVER_UP; info->sinfo[0].change = NS_SERVER_UP; changed = NS_SERVER_UP; } (void) mutex_unlock(&info->mutex[0]); break; } (void) mutex_unlock(&info->mutex[0]); } if (changed) { /* ldap_cachemgr -g option looks up [1] */ (void) mutex_lock(&info->mutex[1]); info->sinfo[1].prev_server_status = info->sinfo[1].server_status; if (changed == NS_SERVER_DOWN) info->sinfo[1].server_status = INFO_SERVER_ERROR; else if (changed == NS_SERVER_UP) info->sinfo[1].server_status = INFO_SERVER_UP; (void) mutex_unlock(&info->mutex[1]); } return (changed); }