| /* |
| * 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. |
| */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <memory.h> |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <libintl.h> |
| #include <syslog.h> |
| #include <sys/door.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <synch.h> |
| #include <pthread.h> |
| #include <unistd.h> |
| #include <lber.h> |
| #include <ldap.h> |
| #include <ctype.h> /* tolower */ |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <arpa/inet.h> |
| #include <ucred.h> |
| #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() { |
| 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; |
| |
| 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, NULL, 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, NULL, 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; |
| |
| 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, |
| NULL, 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, NULL, 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; |
|