/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* tolower */ #include "cachemgr.h" #include "solaris-priv.h" static rwlock_t ldap_lock = DEFAULTRWLOCK; static int sighup_update = FALSE; extern admin_t current_admin; /* 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; /* search timeout (in seconds) */ static int search_timeout = NS_DEFAULT_SEARCH_TIMEOUT; #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_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 *rootDSE_data; char *errormsg; info_rw_t type; info_server_t server_status; info_server_t prev_server_status; info_status_t info_status; } 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; static int getldap_destroy_serverInfo(server_info_t *head); /* * 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 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]); /* 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].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].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; int ldapVersion = LDAP_VERSION3; LDAP *ld; LDAPMessage *resultMsg = NULL; LDAPMessage *e; BerElement *ber; char errmsg[MAXERROR]; char *rootDSE; char *attrs[3]; char *a; char **vals; int ldaperrno = 0; int rc = 0, exitrc = NS_LDAP_SUCCESS; int i = 0, len = 0; pid_t ppid; struct timeval tv; int server_found = 0; 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]); if ((ld = ldap_init(serverInfo->sinfo[1].addr, LDAP_PORT)) == NULL || /* SKIP ldap data base to prevent recursion */ /* in gethostbyname when resolving hostname */ 0 != ldap_set_option(ld, LDAP_X_OPT_DNS_SKIPDB, "ldap")) { (void) mutex_lock(&serverInfo->mutex[1]); serverInfo->sinfo[1].server_status = INFO_SERVER_ERROR; serverInfo->sinfo[1].info_status = INFO_STATUS_ERROR; serverInfo->sinfo[1].errormsg = strdup(gettext("ldap_init failed")); 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); } ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &ldapVersion); ldap_set_option(ld, LDAP_X_OPT_CONNECT_TIMEOUT, &tcptimeout); /* currently, only interested in two attributes */ attrs[0] = "supportedControl"; attrs[1] = "supportedsaslmechanisms"; attrs[2] = NULL; (void) mutex_lock(&serverInfo->mutex[1]); serverInfo->sinfo[1].server_status = INFO_SERVER_CONNECTING; (void) mutex_unlock(&serverInfo->mutex[1]); tv.tv_sec = search_timeout; tv.tv_usec = 0; rc = ldap_search_ext_s(ld, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, 0, NULL, NULL, &tv, 0, &resultMsg); switch (rc) { /* If successful, the root DSE was found. */ case LDAP_SUCCESS: break; /* * If the root DSE was not found, the server does * not comply with the LDAP v3 protocol. */ default: ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ldaperrno); (void) snprintf(errmsg, sizeof (errmsg), gettext(ldap_err2string(ldaperrno))); if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_rootDSE: Root DSE not found." " %s is not an LDAPv3 server (%s).\n", serverInfo->sinfo[1].addr, errmsg); } (void) mutex_lock(&serverInfo->mutex[1]); serverInfo->sinfo[1].errormsg = strdup(errmsg); serverInfo->sinfo[1].info_status = INFO_STATUS_ERROR; serverInfo->sinfo[1].server_status = INFO_SERVER_ERROR; (void) mutex_unlock(&serverInfo->mutex[1]); if (resultMsg) ldap_msgfree(resultMsg); ldap_unbind(ld); /* * sync sinfo copies in the serverInfo. * protected by mutex */ sync_current_with_update_copy(serverInfo); thr_exit((void *) -1); break; } if ((e = ldap_first_entry(ld, resultMsg)) != NULL) { /* calculate length of root DSE data */ for (a = ldap_first_attribute(ld, e, &ber); a != NULL; a = ldap_next_attribute(ld, e, ber)) { if ((vals = ldap_get_values(ld, e, a)) != NULL) { for (i = 0; vals[i] != NULL; i++) { len += strlen(a) + strlen(vals[i]) + strlen(DOORLINESEP) +1; } ldap_value_free(vals); } ldap_memfree(a); } if (ber != NULL) ber_free(ber, 0); /* copy root DSE data */ if (len) { /* add 1 for the last '\0' */ rootDSE = (char *)malloc(len + 1); if (rootDSE != NULL) { /* make it an empty string first */ *rootDSE = '\0'; for (a = ldap_first_attribute(ld, e, &ber); a != NULL; a = ldap_next_attribute( ld, e, ber)) { if ((vals = ldap_get_values( ld, e, a)) != NULL) { for (i = 0; vals[i] != NULL; i++) { int len; len = strlen(a) + strlen(vals[i]) + strlen(DOORLINESEP) + 2; (void) snprintf( rootDSE + strlen(rootDSE), len, "%s=%s%s", a, vals[i], DOORLINESEP); } ldap_value_free(vals); } ldap_memfree(a); } if (ber != NULL) ber_free(ber, 0); } else len = 0; } } /* error, if no root DSE data */ (void) mutex_lock(&serverInfo->mutex[1]); if (len == 0) { serverInfo->sinfo[1].errormsg = strdup(gettext("No root DSE data returned.")); if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_rootDSE: %s.\n", serverInfo->sinfo[1].errormsg); } serverInfo->sinfo[1].type = INFO_RW_UNKNOWN; serverInfo->sinfo[1].info_status = INFO_STATUS_ERROR; serverInfo->sinfo[1].server_status = INFO_SERVER_ERROR; exitrc = -1; } else { /* 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; exitrc = NS_LDAP_SUCCESS; } (void) mutex_unlock(&serverInfo->mutex[1]); if (resultMsg) ldap_msgfree(resultMsg); ldap_unbind(ld); /* * 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].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].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) { 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); /* get search timeout value */ search_timeout = NS_DEFAULT_SEARCH_TIMEOUT; (void) __ns_ldap_getParam(NS_LDAP_SEARCH_TIME_P, ¶mVal, &error); if (paramVal != NULL && *paramVal != NULL) { search_timeout = **((int **)paramVal); (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 (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", req, 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. * output: * a buffer containing server info in the following format: * serveraddress DOORLINESEP [ attr=value [DOORLINESEP attr=value ]...] * for example: ( here | used as DOORLINESEP for visual purposes) * 1.2.3.4|supportedControl=1.1.1.1|supportedSASLmechanisms=EXTERNAL * 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; int matched = FALSE, len; 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; input[strlen(NS_CACHE_NEW)] = '\0'; addr = input + strlen(DOORLINESEP) + strlen(NS_CACHE_NEW); } /* * 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 the server has already been removed, * don't bother */ if (info->sinfo[0].server_status == INFO_SERVER_REMOVED) { (void) mutex_unlock(&info->mutex[0]); 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. */ info->sinfo[0].prev_server_status = info->sinfo[0].server_status; info->sinfo[0].server_status = INFO_SERVER_REMOVED; /* * make sure this will be seen * if a user query the server * status via the ldap_cachemgr's * -g option */ info->sinfo[1].server_status = INFO_SERVER_REMOVED; *svr_removed = TRUE; (void) mutex_unlock(&info->mutex[0]); continue; } } 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) { len = strlen(server->sinfo[0].addr) + strlen(server->sinfo[0].rootDSE_data) + strlen(DOORLINESEP) + 1; *output = (char *)malloc(len); if (*output == NULL) { (void) mutex_unlock(&info->mutex[0]); return (NS_LDAP_MEMORY); } (void) snprintf(*output, len, "%s%s%s", server->sinfo[0].addr, 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; static struct timespec timeout; struct timespec new_timeout; struct timeval tp; static time_t prev_refresh = 0, next_refresh = 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); (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); (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); /* * 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; /* * set cache manager server list TTL if necessary */ if (*output == NULL || server_removed) { (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(ldap_return_t *out, ldap_call_t *in) { char *outstr = NULL; char req[] = "0"; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_getserver()...\n"); } /* assume no server found */ out->ldap_errno = -1; out->ldap_return_code = NOTFOUND; out->ldap_bufferbytesused = sizeof (*out); /* 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, &outstr); if (outstr == NULL) return; out->ldap_bufferbytesused = sizeof (ldap_return_t); (void) strncpy(out->ldap_u.config, outstr, strlen(outstr)+1); if (current_admin.debug_level >= DBG_PROFILE_REFRESH) { /* Log server IP */ char *ptr; ptr = strstr(outstr, DOORLINESEP); if (ptr) { *ptr = '\0'; logit("getldap_getserver: got server %s\n", outstr); } else logit("getldap_getserver: Missing %s." " Internal error\n", DOORLINESEP); } free(outstr); out->ldap_return_code = SUCCESS; out->ldap_errno = 0; } void getldap_get_cacheData(ldap_return_t *out, ldap_call_t *in) { char *outstr = NULL, *instr = NULL; int datatype = CACHE_MAP_UNKNOWN; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_cacheData()...\n"); } /* assume no cache data found */ out->ldap_errno = -1; out->ldap_return_code = NOTFOUND; out->ldap_bufferbytesused = sizeof (*out); /* 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, &outstr); if (outstr == NULL) return; out->ldap_bufferbytesused = sizeof (ldap_return_t); (void) strncpy(out->ldap_u.config, outstr, strlen(outstr)+1); free(outstr); out->ldap_return_code = SUCCESS; out->ldap_errno = 0; } void getldap_set_cacheData(ldap_return_t *out, 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"); } /* assume error */ out->ldap_errno = -1; out->ldap_return_code = NOTFOUND; out->ldap_bufferbytesused = sizeof (*out); /* 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; instr1 = strstr(in->ldap_u.servername, DOORLINESEP); if (instr1 == NULL) return; *instr1 = '\0'; instr1 += strlen(DOORLINESEP); if (*instr1 == '\0') return; instr2 = strstr(instr1, DOORLINESEP); if (instr2 == NULL) return; *instr2 = '\0'; instr2 += strlen(DOORLINESEP); if (*instr2 == '\0') return; rc = getldap_cache_op(CACHE_OP_ADD, datatype, instr1, &instr2); if (rc != NS_LDAP_SUCCESS) return; out->ldap_bufferbytesused = sizeof (ldap_return_t); out->ldap_return_code = SUCCESS; out->ldap_errno = 0; } void getldap_get_cacheStat(ldap_return_t *out) { char *foutstr = NULL; char *soutstr = NULL; char *coutstr = NULL; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_get_cacheStat()...\n"); } /* setup for error return */ out->ldap_errno = -1; out->ldap_return_code = NOTFOUND; out->ldap_bufferbytesused = sizeof (*out); /* 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; } out->ldap_bufferbytesused = sizeof (ldap_return_t); (void) strncpy(out->ldap_u.config, foutstr, strlen(foutstr) + 1); (void) strncat(out->ldap_u.config, soutstr, strlen(soutstr) + 1); (void) strncat(out->ldap_u.config, coutstr, strlen(coutstr) + 1); free(foutstr); free(soutstr); free(coutstr); out->ldap_return_code = SUCCESS; out->ldap_errno = 0; } 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() { 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 %d " "seconds\n", *ptr->paramList[NS_LDAP_EXP_P].ns_pi); } /* set ptr as current_config */ __s_api_init_config(ptr); 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; 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); return (0); } static void perform_update(void) { ns_ldap_error_t *error; struct timeval tp; char buf[20]; int rc; if (current_admin.debug_level >= DBG_ALL) { logit("perform_update()...\n"); } (void) __ns_ldap_setServer(TRUE); if (gettimeofday(&tp, NULL) != 0) return; /* * set the profile TTL parameter, just * in case that the downloading of * the profile from server would fail */ if (current_admin.debug_level >= DBG_PROFILE_REFRESH) { logit("perform_update: current profile TTL is %d seconds\n", current_admin.ldap_stat.ldap_ttl); } /* * 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); __ns_ldap_freeError(&error); return; } (void) rw_wrlock(&ldap_lock); sighup_update = FALSE; (void) rw_unlock(&ldap_lock); do { rc = update_from_profile(); if (rc != 0) { logit("Error: Unable to update from profile\n"); } } while (checkupdate(sighup_update) == TRUE); /* * 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; } (void) rw_rdlock(&ldap_lock); if ((error = __ns_ldap_DumpConfiguration(NSCONFIGREFRESH)) != NULL) { logit("Error: __ns_ldap_DumpConfiguration(\"%s\") failed, " "status: %d " "message: %s\n", NSCONFIGREFRESH, error->status, error->message); __ns_ldap_freeError(&error); } if ((error = __ns_ldap_DumpConfiguration(NSCREDREFRESH)) != NULL) { logit("Error: __ns_ldap_DumpConfiguration(\"%s\") failed, " "status: %d " "message: %s\n", NSCREDREFRESH, error->status, error->message); __ns_ldap_freeError(&error); } if (rename(NSCONFIGREFRESH, NSCONFIGFILE) != 0) logit("Error: unlink failed - errno: %d\n", errno); if (rename(NSCREDREFRESH, NSCREDFILE) != 0) logit("Error: unlink failed - errno: %d\n", errno); (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_lookup(ldap_return_t *out, ldap_call_t *in) { LineBuf configinfo; ns_ldap_error_t *error; if (current_admin.debug_level >= DBG_ALL) { logit("getldap_lookup()...\n"); } (void) rw_rdlock(&ldap_lock); if ((error = __ns_ldap_LoadDoorInfo(&configinfo, in->ldap_u.domainname)) != NULL) { if (error != NULL && error->message != NULL) logit("Error: ldap_lookup: %s\n", error->message); (void) __ns_ldap_freeError(&error); out->ldap_errno = -1; out->ldap_return_code = NOTFOUND; out->ldap_bufferbytesused = sizeof (*out); } else { out->ldap_bufferbytesused = sizeof (ldap_return_t); (void) strncpy(out->ldap_u.config, configinfo.str, configinfo.len); out->ldap_return_code = SUCCESS; out->ldap_errno = 0; } if (configinfo.str != NULL) { free(configinfo.str); configinfo.str = NULL; configinfo.alloc = 0; configinfo.len = 0; } (void) rw_unlock(&ldap_lock); }