/* * 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" /* * DESCRIPTION: Contains top level functions to read/write to the DIT. These * are the API between the shim and the mapping system. * Things calling these should have no knowledge of LDAP. Things * called by them should have no knowledge of NIS. * * Error handling here may appear to be limited but, because the * NIS protocol cannot carry meaningful information about why a * N2L operation failed, functions that don't work log * an error and then just return FAILURE. * */ /* * Includes. WE WANT TO USE REAL DBM FUNCTIONS SO DO NOT INCLUDE SHIM_HOOKS.H. */ #include #include #include #include #include #include #include #include #include "ypsym.h" #include "ypdefs.h" #include "shim.h" #include "../ldap_structs.h" #include "../ldap_parse.h" #include "../nisdb_ldap.h" #include "../ldap_util.h" #include "../ldap_op.h" #include "../ldap_attr.h" #include "../nis_parse_ldap_conf.h" #include "../nisdb_mt.h" #include "yptol.h" #include "dit_access_utils.h" #include "stdio.h" /* Enable standard YP code features defined in ypdefs.h */ USE_YP_PREFIX USE_YP_MASTER_NAME USE_YP_LAST_MODIFIED USE_YP_INPUT_FILE USE_YP_OUTPUT_NAME USE_YP_DOMAIN_NAME USE_YP_SECURE USE_YP_INTERDOMAIN /* * Decs */ suc_code add_special_entries(DBM *, map_ctrl *, bool_t *); void free_null_terminated_list(char **list); /* * FUNCTION: is_yptol_mode(); * * DESCRIPTION: Determines if we should run in N2L or traditional mode based * on the presence of the N2L mapping file. If there are problems * with the file, e.g. unreadable, this will be picked up latter. * * INPUTS: Nothing * * OUTPUTS: TRUE = Run in N2L mode * FALSE = Run in traditional mode. */ bool_t is_yptol_mode() { struct stat filestat; if (stat(YP_DEFAULTCONFFILE, &filestat) != -1) return (TRUE); return (FALSE); } /* * FUNCTION: read_from_dit(); * * DESCRIPTION: Read (i.e. get and map) a single NIS entry from the LDAP DIT. * Also handles retry attempts, on failure, and interpretation of * internal error codes. * * INPUTS: Map name (unqualified) * Domain name * Entry key * Pointer to return location * * OUTPUTS: If successful DBM datum containing result. * On error DBM datum pointing to NULL and, if the cached value * is not to be used, an error code. */ int read_from_dit(char *map, char *domain, datum *key, datum *value) { int count; int res; __nisdb_retry_t *retrieveRetry; /* Initialize tsd */ __nisdb_get_tsd()->domainContext = 0; __nisdb_get_tsd()->escapeFlag = '\0'; for (count = 0; count < ypDomains.numDomains; count++) { if (0 == ypDomains.domainLabels[count]) continue; if (0 == strcasecmp(domain, ypDomains.domainLabels[count])) { __nisdb_get_tsd()->domainContext = ypDomains.domains[count]; break; } } retrieveRetry = &ldapDBTableMapping.retrieveErrorRetry; /* Loop 'attempts' times of forever if -1 */ for (count = retrieveRetry->attempts; (0 <= count) || (-1 == retrieveRetry->attempts); count --) { if (TRUE == singleReadFromDIT(map, domain, key, value, &res)) /* It worked, return value irrelevant */ return (0); if (LDAP_TIMEOUT == res) { /* Exceeded search timeout */ value->dptr = NULL; return (0); } if (is_fatal_error(res)) break; /* * Didn't work. If not the special case where no repeats are * done sleep. */ if (0 != retrieveRetry->attempts) (void) poll(NULL, 0, retrieveRetry->timeout*1000); } /* Make sure returned pointer is NULL */ value->dptr = NULL; /* If we get here access failed work out what to return */ if (ldapDBTableMapping.retrieveError == use_cached) return (0); return (res); } /* * FUNCTION: write_to_dit(); * * DESCRIPTION: Maps and writes a NIS entry to the LDAP DIT. * Also handles retry attempts, on failure, and interpretation of * internal error codes. * * INPUTS: Pointer to (unqualified) map name * Pointer to domain name * The entries key * What to write * Replace flag indicating * TRUE = Replace (overwrite) any existing entries * FALSE = Return error if there are existing entries * Flag indicating if we should tolerate mapping errors. * * OUTPUTS: SUCCESS = Write was successful * FAILURE = Write failed * */ suc_code write_to_dit(char *map, char *domain, datum key, datum value, bool_t replace, bool_t ignore_map_errs) { int count; int res; __nisdb_retry_t *storeRetry = &ldapDBTableMapping.storeErrorRetry; /* Initialize tsd */ __nisdb_get_tsd()->domainContext = 0; __nisdb_get_tsd()->escapeFlag = '\0'; for (count = 0; count < ypDomains.numDomains; count++) { if (0 == ypDomains.domainLabels[count]) continue; if (0 == strcasecmp(domain, ypDomains.domainLabels[count])) { __nisdb_get_tsd()->domainContext = ypDomains.domains[count]; break; } } storeRetry = &ldapDBTableMapping.storeErrorRetry; /* Loop 'attempts' times of forever if -1 */ for (count = storeRetry->attempts; (0 <= count) || (-1 == storeRetry->attempts); count --) { res = singleWriteToDIT(map, domain, &key, &value, replace); if (LDAP_SUCCESS == res) return (SUCCESS); if (is_fatal_error(res)) { /* * The mapping failed and will fail again if it is * retried. However there are some cases where an * actual mapping fault (rather than a LDAP problem) * may be ignored. */ if (ignore_map_errs) { switch (res) { case LDAP_INVALID_DN_SYNTAX: case LDAP_OBJECT_CLASS_VIOLATION: case LDAP_NOT_ALLOWED_ON_RDN: case MAP_NAMEFIELD_MATCH_ERROR: case MAP_NO_DN: return (SUCCESS); default: break; } } return (FAILURE); } if (ldapDBTableMapping.storeError != sto_retry) return (FAILURE); /* * Didn't work. If not the special case where no repeats are * done sleep. */ if (0 != storeRetry->attempts) (void) poll(NULL, 0, storeRetry->timeout*1000); } return (FAILURE); } /* * FUNCTION : get_ttl_value() * * DESCRIPTION: Get the TTL value, derived from mapping file or DIT, for a * entry. * * GIVEN : Pointer to map * A flag indication if TTL should be max, min or random * * RETURNS : TTL value in seconds. * -1 on failure */ int get_ttl_value(map_ctrl *map, TTL_TYPE type) { __nis_table_mapping_t *table_map; struct timeval ret; int interval, res; char *myself = "get_ttl_value"; /* Get the mapping structure corresponding to `map.domain' */ table_map = mappingFromMap(map->map_name, map->domain, &res); if (0 == table_map) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "Get TTL request could not access map %s in domain %s " "(error %d)", map->map_name, map->domain, res); return (-1); } switch (type) { case TTL_MAX: return (table_map->initTtlHi); case TTL_MIN: return (table_map->initTtlLo); default: logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s passed illegal TTL type (%d)", myself, type); /* If unknown TTL type drop through to TTL_RAND */ case TTL_RAND: interval = table_map->initTtlHi - table_map->initTtlLo; if (0 >= interval) return (table_map->initTtlLo); /* * Must get a random value. We assume srand48() got * called at initialization. */ return (lrand48() % interval); case TTL_RUNNING: return (table_map->ttl); } } /* * FUNCTION : get_mapping_domain_list() * * DESCRIPTION: Gets a list of domain names specified, by nisLDAPdomainContext * attributes, in the mapping file. This is used only for initial * DIT setup. Once the DIT has been set up get_domain_list() is * used instead. * * GIVEN : Pointer returned array. * * RETURNS : Number of element in returned array. * Array of elements this is in static memory * and must not be freed by the caller. */ int get_mapping_domain_list(char ***ptr) { *ptr = ypDomains.domainLabels; return (ypDomains.numDomains); } /* * FUNCTION : get_mapping_yppasswdd_domain_list() * * DESCRIPTION: Gets a list of domain names specified, by the * nisLDAPyppasswddDomains attribute, in the mapping file. This * is the list of domains for which passwords should be changed. * * GIVEN : Pointer returned array * * RETURNS : Number of element in returned array. * 0 if no nisLDAPyppasswddDomains attribute is present. * Array of elements this is in static memory * and must not be freed by the caller. */ int get_mapping_yppasswdd_domain_list(char ***ptr) { *ptr = ypDomains.yppasswddDomainLabels; return (ypDomains.numYppasswdd); } /* * FUNCTION : free_map_list() * * DESCRIPTION: Frees a map list. * * GIVEN : Pointer to the map list. * * RETURNS : Nothing */ void free_map_list(char **map_list) { free_null_terminated_list(map_list); } /* * FUNCTION : get_passwd_list() * * DESCRIPTION: Gets a list of either passwd or passwd.adjunct map files * defined in the mapping file. These are the files which have * 'magic' nisLDAPdatabaseIdMapping entries aliasing them to * passwd or passwd.adjunct. This function is required so that * yppasswdd can work out which maps to synchronize with any * password changes. * * This information is not currently stored by the parser but * we can recover it from the hash table. This makes hard work but * passwords should not be changed very frequently * * GIVEN : Flag indicating if a list is required for passwd or * passwd.adjunct * Domain to return the list for. * * RETURNS : Null terminated list of map names in malloced memory. To be * freed by caller. (Possibly empty if no passwd maps found) * NULL on error */ #define ARRAY_CHUNK 10 char ** get_passwd_list(bool_t adjunct, char *domain) { char *myself = "get_passwd_list"; __nis_hash_item_mt *it; int i, size; char *end_ptr, *copy_ptr; char *target; /* What we are looking for */ int target_len; int domain_len; char **res; /* Result array */ char **res_old; /* Old value of res during realloc */ int array_size; /* Current malloced size */ int res_count = 0; /* Current result count */ /* * Always need an array even if just for terminator. Normally one * chunk will be enough. */ res = am(myself, ARRAY_CHUNK * sizeof (char *)); if (NULL == res) return (NULL); array_size = ARRAY_CHUNK; /* Set up target */ if (adjunct) target = PASSWD_ADJUNCT_PREFIX; else target = PASSWD_PREFIX; target_len = strlen(target); domain_len = strlen(domain); /* Work out hash table length */ size = sizeof (ldapMappingList.keys) / sizeof (ldapMappingList.keys[0]); /* For all hash table entries */ for (i = 0; i < size; i++) { /* Walk linked list for this hash table entry */ for (it = ldapMappingList.keys[i]; NULL != it; it = it->next) { /* Check right map */ if ((target_len + domain_len + 1) > strlen(it->name)) continue; if (0 != strncmp(it->name, target, target_len)) continue; /* Check right domain (minus trailing dot) */ if (strlen(domain) >= strlen(it->name)) continue; end_ptr = it->name + strlen(it->name) - strlen(domain) - 1; if (',' != *(end_ptr - 1)) continue; if (0 != strncmp(end_ptr, domain, strlen(domain))) continue; /* Check if we need to enlarge array */ if ((res_count + 1) >= array_size) { array_size += ARRAY_CHUNK; res_old = res; res = realloc(res, array_size * sizeof (char *)); if (NULL == res) { res_old[res_count] = NULL; free_passwd_list(res_old); return (NULL); } } /* What we really need is strndup() */ res[res_count] = am(myself, end_ptr - it->name + 1); if (NULL == res[res_count]) { free_passwd_list(res); return (NULL); } /* Copy from start to end_ptr */ memcpy(res[res_count], it->name, end_ptr-it->name - 1); res_count ++; } } /* Terminate array */ res[res_count] = NULL; return (res); } /* * FUNCTION : free_passwd_list() * * DESCRIPTION: Frees a password list obtained with get_passwd_list() * * INPUTS : Address of list to free. * * OUTPUTS : Nothing */ void free_passwd_list(char **list) { free_null_terminated_list(list); } /* * FUNCTION : free_null_terminated_list() * * DESCRIPTION: Frees a generic null terminated list. * * INPUTS : Address of list to free. * * OUTPUTS : Nothing */ void free_null_terminated_list(char **list) { int index; /* Free all the strings */ for (index = 0; NULL != list[index]; index ++) sfree(list[index]); /* Free the array */ sfree(list); } /* * FUNCTION : add_special_entries() * * DESCRIPTION: Adds the special (YP_*) entries to a map. * * Part of dit_access because requires access to the mapping * file in order to work out if secure and interdomain entries * should be created. * * GIVEN : Pointer to an open, temporary, DBM file * Pointer to map information (do not use DBM fields). * Pointer to a location in which to return security flag * * RETURNS : SUCCESS = All entries created * FAILURE = Some entries not created */ suc_code add_special_entries(DBM *db, map_ctrl *map, bool_t *secure_flag) { char local_host[MAX_MASTER_NAME]; char time_string[MAX_ASCII_ORDER_NUMBER_LENGTH]; struct timeval now; __nis_table_mapping_t *table_map; int res; /* Last modified time is now */ update_timestamp(db); /* Add domain name */ addpair(db, yp_domain_name, map->domain); /* For N2L input and output file are meaningless */ /* addpair(db, yp_input_file, infilename); */ /* addpair(db, yp_output_file, outfilename); */ /* For N2L mode local machine is always the master */ sysinfo(SI_HOSTNAME, local_host, sizeof (local_host)); addpair(db, yp_master_name, local_host); /* Get the mapping structure corresponding to `map.domain' */ table_map = mappingFromMap(map->map_name, map->domain, &res); if (0 == table_map) return (FAILURE); /* Add secure and interdomain flags if required */ if (table_map->securemap_flag) { addpair(db, yp_secure, ""); *secure_flag = TRUE; } else { *secure_flag = FALSE; } if (table_map->usedns_flag) addpair(db, yp_interdomain, ""); return (SUCCESS); } /* * FUNCTION: update_map_from_dit() * * DESCRIPTION: Core code called to update an entire map. * Information is recovered from LDAP and used to build a duplicate * copy of the live maps. When this is complete the maps are * locked and then overwritten by the new copy. * * INPUTS: map_ctrl containing lots of information about the map and a * pointer to it's lock which will be required. * Flag indicating if progress logging is required. * * OUTPUTS: SUCCESS = Map updated * FAILURE = Map not updated */ suc_code update_map_from_dit(map_ctrl *map, bool_t log_flag) { __nis_table_mapping_t *t; __nis_rule_value_t *rv, *frv; __nis_ldap_search_t *ls; __nis_object_dn_t *objectDN = NULL; datum *datval, *datkey; int nr = 0, i, j, nv, numDNs; int statP = SUCCESS, flag; char *objname, **dn; /* Name of temporary entries DBM file */ char *temp_entries; /* Name of temporary TTL DBM file */ char *temp_ttl; /* Temporary DBM handles */ DBM *temp_entries_db; DBM *temp_ttl_db; map_ctrl temp_map; datum key; char *myself = "update_map_from_dit"; bool_t secure_flag; int entry_count = 1; int next_print = PRINT_FREQ; if (!map || !map->map_name || !map->domain) { return (FAILURE); } __nisdb_get_tsd()->escapeFlag = '\0'; /* * netgroup.byxxx maps are a special case. They are regenerated from * the netgroup map, not the DIT, so handle special case. */ if ((0 == strcmp(map->map_name, NETGROUP_BYHOST)) || 0 == (strcmp(map->map_name, NETGROUP_BYUSER))) { return (update_netgroup_byxxx(map)); } /* Get the mapping information for the map */ if ((t = mappingFromMap(map->map_name, map->domain, &statP)) == 0) { if (statP == MAP_NO_MAPPING_EXISTS) logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: No mapping information available for %s,%s", myself, map->map_name, map->domain); return (FAILURE); } /* Allocate and set up names */ if (SUCCESS != alloc_temp_names(map->map_path, &temp_entries, &temp_ttl)) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Unable to create map names for %s", myself, map->map_path); return (FAILURE); } /* Create temp entry and TTL file */ if ((temp_entries_db = dbm_open(temp_entries, O_RDWR | O_CREAT, 0644)) == NULL) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s", myself, temp_entries); sfree(temp_entries); sfree(temp_ttl); return (FAILURE); } if ((temp_ttl_db = dbm_open(temp_ttl, O_RDWR | O_CREAT, 0644)) == NULL) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s", myself, temp_ttl); dbm_close(temp_entries_db); delete_map(temp_entries); sfree(temp_entries); sfree(temp_ttl); return (FAILURE); } /* Initialize domainContext tsd */ __nisdb_get_tsd()->domainContext = 0; for (i = 0; i < ypDomains.numDomains; i++) { if (0 == ypDomains.domainLabels[i]) continue; if (0 == strcasecmp(map->domain, ypDomains.domainLabels[i])) { __nisdb_get_tsd()->domainContext = ypDomains.domains[i]; break; } } if (!(objname = getFullMapName(map->map_name, map->domain))) { if (temp_entries_db) dbm_close(temp_entries_db); if (temp_ttl_db) dbm_close(temp_ttl_db); delete_map(temp_entries); sfree(temp_entries); delete_map(temp_ttl); sfree(temp_ttl); return (FAILURE); } /* Try each mapping for the map */ for (flag = 0; t != 0; t = t->next) { /* Check if the mapping is the correct one */ if (strcmp(objname, t->objName) != 0) { continue; } /* Check if rulesFromLDAP are provided */ if (t->numRulesFromLDAP == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: No rulesFromLDAP available for %s (%s)", myself, t->dbId, map->map_name); continue; } /* Set flag to indicate update is enabled */ flag = 1; /* Create ldap request for enumeration */ for (objectDN = t->objectDN; objectDN && objectDN->read.base; objectDN = objectDN->next) { if ((ls = createLdapRequest(t, 0, 0, 1, NULL, objectDN)) == 0) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Failed to create " "ldapSearch request for " "%s (%s) for base %s", myself, t->dbId, map->map_name, objectDN->read.base); statP = FAILURE; break; } if (log_flag) { printf("Waiting for LDAP search results.\n"); } /* Query LDAP */ nr = (ls->isDN)?0:-1; rv = ldapSearch(ls, &nr, 0, &statP); freeLdapSearch(ls); if (rv == 0) { if (statP == LDAP_NO_SUCH_OBJECT) { /* * No Entry exists in the ldap server. Not * a problem. Maybe there are just no entries * in this map. */ continue; } logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: ldapSearch error %d " "(%s) for %s (%s) for base %s", myself, statP, ldap_err2string(statP), t->dbId, map->map_name, objectDN->read.base); statP = FAILURE; break; } if (log_flag) { printf("Processing search results.\n"); } /* Obtain list of DNs for logging */ if ((dn = findDNs(myself, rv, nr, 0, &numDNs)) == 0) { statP = FAILURE; break; } /* For each entry in the result do the following */ for (i = 0; i < nr; i++) { /* Convert LDAP data to NIS equivalents */ statP = buildNISRuleValue(t, &rv[i], map->domain); if (statP == MAP_INDEXLIST_ERROR) continue; if (statP != SUCCESS) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Conversion error %d (LDAP to " "name=value pairs) " "for (dn: %s) for " "%s (%s) for base %s", myself, statP, NIL(dn[i]), t->dbId, map->map_name, objectDN->read.base); continue; } /* Obtain the datum for value */ datval = ruleValueToDatum(t, &rv[i], &statP); if (datval == 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Conversion error %d " "(name=value pairs to NIS)" " for (dn: %s) for " "%s (%s) for base %s", myself, statP, NIL(dn[i]), t->dbId, map->map_name, objectDN->read.base); continue; } /* Obtain the datum for key */ datkey = getKeyFromRuleValue(t, &rv[i], &nv, &statP); if (datkey == 0) { logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: Unable to obtain NIS " "key from LDAP data (dn:%s) " "for %s (%s) for base %s", myself, NIL(dn[i]), t->dbId, map->map_name, objectDN->read.base); sfree(datval->dptr); sfree(datval); continue; } /* Write to the temporary map */ for (j = 0; j < nv; j++, entry_count ++) { if (datkey[j].dsize == 0) continue; errno = 0; /* DBM_INSERT to match */ /* singleReadFromDIT */ if (dbm_store(temp_entries_db, datkey[j], *datval, DBM_INSERT) < 0) { /* * For some cases errno may * still be 0 but dbm_error * isn't informative at all. */ logmsg(MSG_NOTIMECHECK, LOG_WARNING, "%s: dbm store error " "(errno=%d) " "for (key=%s, value=%s) " "for %s (%s) for base %s", myself, errno, datkey[j].dptr, datval->dptr, t->dbId, map->map_name, objectDN->read.base); /* clear the error */ dbm_clearerr(temp_entries_db); } sfree(datkey[j].dptr); if (log_flag && (entry_count >= next_print)) { printf("%d entries processed\n", entry_count); next_print *= 2; } } sfree(datkey); sfree(datval->dptr); sfree(datval); } freeRuleValue(rv, nr); freeDNs(dn, numDNs); } /* End of for over objectDN */ } sfree(objname); if (t != 0 || flag == 0) { if (temp_entries_db) dbm_close(temp_entries_db); if (temp_ttl_db) dbm_close(temp_ttl_db); delete_map(temp_entries); sfree(temp_entries); delete_map(temp_ttl); sfree(temp_ttl); return (statP); } /* Set up enough of map_ctrl to call update_entry_ttl */ temp_map.map_name = map->map_name; temp_map.domain = map->domain; temp_map.ttl = temp_ttl_db; /* Generate new TTL file */ key = dbm_firstkey(temp_entries_db); while (key.dptr != 0) { if (!is_special_key(&key)) /* * We don't want all the entries to time out at the * same time so create random TTLs. */ if (FAILURE == update_entry_ttl(&temp_map, &key, TTL_RAND)) logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not update TTL for " "(key=%s) for map %s,%s", myself, NIL(key.dptr), map->map_name, map->domain); key = dbm_nextkey(temp_entries_db); } /* Update map TTL */ if (SUCCESS != update_map_ttl(&temp_map)) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not update map TTL " "for %s,%s", myself, map->map_name, map->domain); } /* Set up 'special' nis entries */ add_special_entries(temp_entries_db, map, &secure_flag); /* Close temp DBM files */ dbm_close(temp_entries_db); dbm_close(temp_ttl_db); /* Lock access to the map for copy */ lock_map_ctrl(map); /* Move temp maps to real ones */ rename_map(temp_entries, map->map_path, secure_flag); rename_map(temp_ttl, map->ttl_path, secure_flag); /* Free file names */ sfree(temp_entries); sfree(temp_ttl); /* Unlock map */ unlock_map_ctrl(map); return (SUCCESS); } /* * FUNCTION : get_mapping_map_list() * * DESCRIPTION: Gets a list of nis maps for a given domain specified in the * mapping file. This information is not saved so have to go * through the entire hash table. At least this is only done at * initialization time. * * GIVEN : Domain name * * RETURNS : List of map names in malloced memory. MUST BE FREED BY CALLER. */ char ** get_mapping_map_list(char *domain) { char *myself = "get_mapping_map_list"; __nis_hash_item_mt *it; int i, j, size; char *end_ptr, *copy_ptr; char **res; /* Result array */ char **res_old; /* Old value of res during realloc */ int array_size; /* Current malloced size */ int res_count = 0; /* Current result count */ /* * Always need an array even if just for terminator. Normally one * chunk will be enough. */ res = am(myself, ARRAY_CHUNK * sizeof (char *)); if (NULL == res) return (NULL); array_size = ARRAY_CHUNK; /* Work out hash table length */ size = sizeof (ldapMappingList.keys) / sizeof (ldapMappingList.keys[0]); /* For all hash table entries */ for (i = 0; i < size; i++) { /* Walk linked list for this hash table entry */ for (it = ldapMappingList.keys[i]; NULL != it; it = it->next) { /* Check it's not a split field entry */ if (0 != ((__nis_table_mapping_t *)it)->numSplits) continue; /* Check right domain (minus trailing dot) */ if (strlen(domain) >= strlen(it->name)) continue; end_ptr = it->name + strlen(it->name) - strlen(domain) - 1; if (',' != *(end_ptr - 1)) continue; if (0 != strncmp(end_ptr, domain, strlen(domain))) continue; /* Check if we need to enlarge array */ if ((res_count + 1) >= array_size) { array_size += ARRAY_CHUNK; res_old = res; res = realloc(res, array_size * sizeof (char *)); if (NULL == res) { res_old[res_count] = NULL; free_passwd_list(res_old); return (NULL); } } /* * We will need the sequence number when we come to * sort the entries so for now store a pointer to * the __nis_hash_item_mt. */ res[res_count] = (char *)it; res_count ++; } } /* Terminate array */ res[res_count] = NULL; /* Bubble sort entries into the same order as mapping file */ for (i = res_count - 2; 0 <= i; i--) { for (j = 0; j <= i; j++) { if (((__nis_table_mapping_t *)res[j + 1])->seq_num < ((__nis_table_mapping_t *)res[j])->seq_num) { end_ptr = res[j]; res[j] = res[j+1]; res[j + 1] = end_ptr; } } } /* Finally copy the real strings in to each entry */ for (i = 0; NULL != res[i]; i ++) { /* Get hash table entry back */ it = (__nis_hash_item_mt *)res[i]; end_ptr = it->name + strlen(it->name) - strlen(domain) - 1; /* What we really need is strndup() */ res[i] = am(myself, end_ptr - it->name + 1); if (NULL == res[i]) { free_map_list(res); return (NULL); } /* Copy from start to end_ptr */ memcpy(res[i], it->name, end_ptr-it->name - 1); } return (res); } /* * FUNCTION : make_nis_container() * * DESCRIPTION: Sets up container for map_name in the DIT. * * GIVEN : Map name * The domain name. * Flag indicating if container should be created. * * RETURNS : SUCCESS = It worked * FAILURE = There was a problem. */ suc_code make_nis_container(char *map_name, char *domain, bool_t init_containers) { int i, rc, statP = SUCCESS; __nis_table_mapping_t *t; char *dn; char *myself = "make_nis_container"; if (!map_name || !domain) return (FAILURE); if (FALSE == init_containers) { /* * If we are not creating containers it is debatable what we * should do . Maybe we should check for a pre- * existing container and return failure if it does not exist. * * For now we assume the user will not have called us in this * mode unless they know what they are doing. So return * success. If they have got it wrong then latter writes will * fail. */ return (SUCCESS); } /* Get the mapping information for the map */ if ((t = mappingFromMap(map_name, domain, &statP)) == 0) { if (statP == MAP_NO_MAPPING_EXISTS) logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: No mapping information available for %s,%s", myself, NIL(map_name), NIL(domain)); return (FAILURE); } /* Two times. One for readDN and other for writeDN */ for (i = 0; i < 2; i++) { if (i == 0) dn = t->objectDN->read.base; else { if (t->objectDN->write.base == 0) { logmsg(MSG_NOTIMECHECK, LOG_INFO, "%s: No baseDN in writespec. Write " "disabled for %s,%s", myself, map_name, domain); break; } if (!strcasecmp(dn, t->objectDN->write.base)) break; dn = t->objectDN->write.base; } if ((rc = makeNISObject(0, dn)) == FAILURE) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Unable to create ldap container (dn: %s) " "for %s,%s", myself, dn, map_name, domain); return (FAILURE); } } return (SUCCESS); } /* * FUNCTION : make_nis_domain() * * DESCRIPTION: Sets up a nisDomainObject in the DIT * * GIVEN: Name of the domain * Flag indicating if domain should be create or possibly just * checked for. */ suc_code make_nis_domain(char *domain, bool_t init_containers) { if (FALSE == init_containers) { /* * If we are not creating containers it is debatable what we * should do with domains. Maybe we should check for a pre- * existing domain and return failure if it does not exist. * * For now we assume the user will not have called us in this * mode unless they know what they are doing. So return * success. If they have got it wrong then latter writes will * fail. */ return (SUCCESS); } /* Create the domain */ return (makeNISObject(domain, 0)); } /* * FUNCTION: update_netgroup_byxxx() * * DESCRIPTION: Updates the netgroup.byxxx series of maps based on the current * netgroup file. We invoke revnetgroup so that if any changes * are made to this utility the same changes are made here. * * INPUTS: map_ctrl containing lots of information about the map and a * pointer to it's lock which will be required. * * OUTPUTS: SUCCESS = Map updated * FAILURE = Map not updated */ suc_code update_netgroup_byxxx(map_ctrl *map) { /* Name of temporary entries DBM file */ char *temp_entries; /* Name of temporary TTL DBM file */ char *temp_ttl; /* Temporary DBM handles */ DBM *temp_entries_db; DBM *temp_ttl_db; map_ctrl temp_map; char *myself = "update_netgroup_byxxx"; char *cmdbuf; int cmd_length; datum key; map_ctrl *netgroupmap; int res; /* Temporary revnetgroup files */ const char *byhost = NETGROUP_BYHOST "_REV" TEMP_POSTFIX; const char *byuser = NETGROUP_BYUSER "_REV" TEMP_POSTFIX; const char *temp_file_name; /* * We need to use two different temporary files: one for netgroup.byhost * and other for netgroup.byuser, since these two maps can be updated * simultaneously. These temporary files will hold the output of * revnetgroup [-h|-u] command. They are then used to generate the * corresponding dbm files and thereafter deleted. */ if (0 == strcmp(map->map_name, NETGROUP_BYHOST)) temp_file_name = byhost; else temp_file_name = byuser; /* Alloc enough cmd buf for revnet cmd */ cmd_length = strlen("/usr/sbin/makedbm -u ") + (strlen(map->map_path) - strlen(map->map_name)) + strlen(NETGROUP_MAP) + strlen(" | /usr/sbin/revnetgroup -h > ") + (strlen(map->map_path) - strlen(map->map_name)) + strlen(temp_file_name) + 1; cmdbuf = am(myself, cmd_length); if (NULL == cmdbuf) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not alloc cmdbuf.", myself); return (FAILURE); } /* * If necessary update (and wait for) netgroups map. This is a lot of * work but if the netgroup map itself is not being accessed it may * contain information that is not up to date with the DIT. * * We use the cmdbuf to store the qualified netgroup map name there will * be enough space for this but we are not yet creating the cmd. */ strlcpy(cmdbuf, map->map_path, strlen(map->map_path) - strlen(map->map_name) + 1); strcat(cmdbuf, NETGROUP_MAP); netgroupmap = (map_ctrl *)shim_dbm_open(cmdbuf, O_RDWR | O_CREAT, 0644); if (NULL == netgroupmap) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not update %s.", myself, cmdbuf); sfree(cmdbuf); return (FAILURE); } if (has_map_expired(netgroupmap)) { lock_map_ctrl(netgroupmap); update_map_if_required(netgroupmap, TRUE); unlock_map_ctrl(netgroupmap); } shim_dbm_close((DBM *)netgroupmap); /* Dump netgroup file through revnetgroup to a temp file */ strcpy(cmdbuf, "/usr/sbin/makedbm -u "); /* Unmake the netgroup file in same domain as map */ strncat(cmdbuf, map->map_path, strlen(map->map_path) - strlen(map->map_name)); strcat(cmdbuf, NETGROUP_MAP); if (0 == strcmp(map->map_name, NETGROUP_BYHOST)) { strcat(cmdbuf, " | /usr/sbin/revnetgroup -h > "); } else { strcat(cmdbuf, " | /usr/sbin/revnetgroup -u > "); } /* Create temp file file in same domain as map */ strncat(cmdbuf, map->map_path, strlen(map->map_path) - strlen(map->map_name)); strcat(cmdbuf, temp_file_name); if (0 > system(cmdbuf)) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not run \"%s\" " "(errno=%d)", myself, cmdbuf, errno); sfree(cmdbuf); return (FAILURE); } sfree(cmdbuf); /* Allocate and set up names */ if (SUCCESS != alloc_temp_names(map->map_path, &temp_entries, &temp_ttl)) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Unable to create map names for %s", myself, map->map_path); return (FAILURE); } /* Make the temporary DBM file */ cmdbuf = am(myself, strlen("/usr/sbin/makedbm") + (strlen(map->map_path) - strlen(map->map_name)) + strlen(temp_file_name) + strlen(temp_entries) + 3); if (NULL == cmdbuf) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not allocate cmdbuf.", myself); sfree(temp_entries); sfree(temp_ttl); return (FAILURE); } strcpy(cmdbuf, "/usr/sbin/makedbm "); strncat(cmdbuf, map->map_path, strlen(map->map_path) - strlen(map->map_name)); strcat(cmdbuf, temp_file_name); strcat(cmdbuf, " "); strcat(cmdbuf, temp_entries); if (0 > system(cmdbuf)) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not run \"%s\" " "(errno=%d)", myself, cmdbuf, errno); sfree(cmdbuf); sfree(temp_entries); sfree(temp_ttl); return (FAILURE); } /* Already have enough command buffer to rm temporary file */ strlcpy(cmdbuf, map->map_path, strlen(map->map_path) - strlen(map->map_name) + 1); strcat(cmdbuf, temp_file_name); res = unlink(cmdbuf); /* If the temp file did not exist no problem. Probably had no entries */ if ((0 != res) && (ENOENT != errno)) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not delete \"%s\" " "(errno=%d)", myself, cmdbuf, errno); sfree(temp_entries); sfree(temp_ttl); sfree(cmdbuf); return (FAILURE); } sfree(cmdbuf); if ((temp_entries_db = dbm_open(temp_entries, O_RDWR | O_CREAT, 0644)) == NULL) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s", myself, temp_entries); sfree(temp_entries); sfree(temp_ttl); return (FAILURE); } if ((temp_ttl_db = dbm_open(temp_ttl, O_RDWR | O_CREAT, 0644)) == NULL) { logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not open %s", myself, temp_ttl); dbm_close(temp_entries_db); sfree(temp_entries); sfree(temp_ttl); return (FAILURE); } /* * Set up enough of map_ctrl to call update_entry_ttl. Since there is * no mapping, and thus not TTL, defined for these maps use the TTL * values for netgroup map */ temp_map.map_name = NETGROUP_MAP; temp_map.domain = map->domain; temp_map.ttl = temp_ttl_db; /* * Generate new TTL file. Since these maps work only on the whole map * expiry these will not actually be used but there presence makes it * easier to handle these maps in the same way as other maps. */ key = dbm_firstkey(temp_entries_db); while (key.dptr != 0) { if (!is_special_key(&key)) /* * For these maps want all timouts to be maximum */ if (FAILURE == update_entry_ttl(&temp_map, &key, TTL_MAX)) logmsg(MSG_NOTIMECHECK, LOG_ERR, "%s: Could not update TTL for " "(key=%s) for map %s,%s", myself, NIL(key.dptr), map->map_name, map->domain); key = dbm_nextkey(temp_entries_db); } /* Update map TTL */ update_map_ttl(&temp_map); /* Close temp DBM files */ dbm_close(temp_entries_db); dbm_close(temp_ttl_db); /* Lock access to the map for copy */ lock_map_ctrl(map); /* Move temp maps to real ones */ rename_map(temp_entries, map->map_path, FALSE); rename_map(temp_ttl, map->ttl_path, FALSE); /* Free file names */ sfree(temp_entries); sfree(temp_ttl); /* Unlock map */ unlock_map_ctrl(map); return (SUCCESS); }