Mercurial > illumos > illumos-gate
view usr/src/lib/libnsl/nis/gen/nis_groups.c @ 3864:2ae506652d11
PSARC 2007/129 thr_keycreate_once
6513516 double checked locking code needs a memory barrier
author | raf |
---|---|
date | Tue, 20 Mar 2007 17:29:57 -0700 |
parents | e3f7eaf7dde4 |
children | ca4d4a3ddd4c |
line wrap: on
line source
/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * nis_groups.c * * This file contains * (1) Routines to test "is principal A in group B?". The * implementation of this includes code to cache groups, * (2) Operations on groups: add a principal, remove a principal, * check that a group exists, create and destroy groups. */ #include "mt.h" #include <syslog.h> /* ==== Is it really our place to syslog() things? */ #include <string.h> #include <stdlib.h> #include <sys/time.h> #define __NIS_PRIVATE_INTERFACES #include <rpcsvc/nis.h> #include <stdio.h> #include "nis_local.h" /* * Results of do_ismember_2() and lookup_recursive(). ISMEM_DUNNO means * "I couldn't look up the group (or some recursive group), so I don't know". */ enum ismem { ISMEM_DUNNO, ISMEM_NO, ISMEM_YES }; /* === Should be defined with the rest of the hash-table routines */ typedef void (*nis_flush_func)(NIS_HASH_ITEM *); extern void nis_flush_table(NIS_HASH_TABLE *, nis_flush_func); typedef bool_t (*nis_scan_func)(NIS_HASH_ITEM *item, void *funcarg); extern void nis_scan_table(NIS_HASH_TABLE *, nis_scan_func, void *funcarg); /* Forward declarations */ typedef nis_result *(*nis_lookup_func)(nis_name, uint_t); static nis_object *get_group(nis_name, nis_name, nis_lookup_func, nis_error *); static enum ismem do_ismember_2(nis_name, nis_name, nis_name, nis_lookup_func, nis_error *); /* * The top level of the group cache. There is a single instance of this: * it's a hash-table that maps group-names to g_entry structures (q.v.) */ struct g_cache { NIS_HASH_TABLE ht; /* * Cache statistics, available from the server with the TAG_S_GCACHE * tag. */ int ncalls; int nhits; int nmisses; }; typedef struct g_cache *g_cache_ptr; rwlock_t g_cache_lock = DEFAULTRWLOCK; static g_cache_ptr groups_cache; static g_cache_ptr get_g_cache(void) { g_cache_ptr gc; /* always enter with a READ LOCK and exit with one too */ ASSERT(RW_READ_HELD(&g_cache_lock)); if ((gc = groups_cache) != 0) { return (gc); } (void) rw_unlock(&g_cache_lock); /* write lock the cache and try again */ (void) rw_wrlock(&g_cache_lock); if ((gc = groups_cache) != 0) { (void) rw_unlock(&g_cache_lock); (void) rw_rdlock(&g_cache_lock); return (gc); } gc = groups_cache = calloc(1, sizeof (*groups_cache)); if (groups_cache == 0) { (void) rw_unlock(&g_cache_lock); (void) rw_rdlock(&g_cache_lock); return (0); } (void) rw_unlock(&g_cache_lock); (void) rw_rdlock(&g_cache_lock); return (gc); } /* Interface for rpc.nisd and anyone else who wants to know */ int __nis_group_cache_stats(int *grpcachecall, int *grpcachehits, int *grpcachemisses) { (void) rw_rdlock(&g_cache_lock); if (groups_cache == 0) { *grpcachecall = 0; *grpcachehits = 0; *grpcachemisses = 0; (void) rw_unlock(&g_cache_lock); return (0); } *grpcachecall = groups_cache->ncalls; *grpcachehits = groups_cache->nhits; *grpcachemisses = groups_cache->nmisses; (void) rw_unlock(&g_cache_lock); return (1); } /* * Storage for the three sorts of group members: * explicit (name of a principal), * implicit (name of a domain, meaning all principals in that domain) * recursive (name of a group, meaning all principals in that group ) * We expect lots of explicit members, so we use a hash table; for the other * two we use singly-linked lists (if there are lots of implicit members * then a hash table could be used there too, but there'll probably just be * zero or one implicit members). * Memory-management policy: we (or the hash-table routines) malloc/free space * for the tables/lists, but not for the strings they contain; the strings * are all assumed to be pointers into the gr_members<> vector in the * group object, which we keep around. */ struct cons { struct cons *prochain; char *nom; }; struct g_varieties { NIS_HASH_TABLE *explicit; struct cons *implicit; struct cons *recursive; }; typedef struct g_varieties g_varieties; /* * === For our purposes, most of NIS_HASH_TABLE is excess baggage. We don't * need keychain and we don't need the first/prv_item/nxt_item chain. * Unless there's something that really needs stack-ordered semantics, * we should get rid of them and reduce items from 20 bytes to 8. */ static bool_t insert_explicit(g_varieties *varp, nis_name princp) { NIS_HASH_ITEM *it; if (varp->explicit == 0) { if (0 == (varp->explicit = calloc(1, sizeof (NIS_HASH_TABLE)))) { return (FALSE); } } /* Don't use nis_insert_name() because we don't need the strdup(), */ /* so do the same sort of thing ourselves */ if ((it = malloc(sizeof (NIS_HASH_ITEM))) == NULL) { /* Memory is tight; can we free some that we don't need? */ if (varp->explicit->first == 0) { /* Yup, no entries in the hash-table, so free it */ free(varp->explicit); varp->explicit = 0; } return (FALSE); } it->name = princp; if (!nis_insert_item(it, varp->explicit)) { free(it); return (FALSE); } return (TRUE); } static bool_t lookup_explicit(g_varieties *varp, nis_name princp) { int dummy; ASSERT(RW_READ_HELD(&g_cache_lock)); if (varp->explicit == 0) { return (FALSE); } return (nis_in_table(princp, varp->explicit, &dummy) == 1); } static void delete_explicit(g_varieties *varp) { if (varp->explicit != 0) { nis_flush_table(varp->explicit, (nis_flush_func)free); free(varp->explicit); varp->explicit = 0; } } /* ARGSUSED1 */ static bool_t printf_hname(NIS_HASH_ITEM *it, void *dummy) { (void) printf("\t%s\n", it->name); return (FALSE); /* i.e. continue */ } static void printf_explicit(const g_varieties *varp, const char *title) { if (varp->explicit == 0) { (void) printf(" No explicit %smembers\n", title); } else { (void) printf(" Explicit %smembers:\n", title); nis_scan_table(((g_varieties *)varp)->explicit, printf_hname, (void *)0); } } static bool_t push_namelist(struct cons **nlp, char *name) { struct cons *it; /* Don't bother looking for duplicates. They were quite likely */ /* with the old group semantics but would be pretty weird now. */ if ((it = malloc(sizeof (*it))) == NULL) { return (FALSE); } it->nom = name; it->prochain = *nlp; *nlp = it; return (TRUE); } static void free_namelist(struct cons **nlp) { struct cons *cur, *nxt; for (cur = *nlp; cur != 0; cur = nxt) { nxt = cur->prochain; free(cur); } *nlp = 0; } static void print_namelist(const struct cons *nl) { while (nl != 0) { (void) printf("\t%s\n", nl->nom); nl = nl->prochain; } } static bool_t insert_implicit(g_varieties *varp, nis_name implicit) { /* Assumes that the "*." has been stripped from 'implicit' */ return (push_namelist(&varp->implicit, implicit)); } static bool_t lookup_implicit(g_varieties *varp, nis_name princp) { nis_name domain = nis_domain_of(princp); struct cons *nl; ASSERT(RW_READ_HELD(&g_cache_lock)); for (nl = varp->implicit; nl != 0; nl = nl->prochain) { /* ==== Using nis_dir_cmp is silly; just strcasecmp()? */ if (nis_dir_cmp(domain, nl->nom) == SAME_NAME) { return (TRUE); } } return (FALSE); #if 0 /* Alternative semantics, where "*" means a subtree of the */ /* namespace rather than just one level wildcarded. */ for (nl = varp->implicit; nl != 0; nl = nl->prochain) { if (nis_dir_cmp(princp, nl->nom) == LOWER_NAME) return (TRUE); } return (FALSE); #endif } static void delete_implicit(g_varieties *varp) { free_namelist(&varp->implicit); } static void printf_implicit(const g_varieties *varp, const char *title) { if (varp->implicit == 0) { (void) printf(" No implicit %smembers\n", title); } else { (void) printf(" Implicit %smembers:\n", title); print_namelist(varp->implicit); } } static bool_t insert_recursive(g_varieties *varp, nis_name recurs) { /* Assumes the "@" has been stripped from 'recurs' */ return (push_namelist(&varp->recursive, recurs)); } static enum ismem lookup_recursive( g_varieties *varp, nis_name princp, nis_name refname, /* name of the group that contains */ /* these recursive references */ nis_lookup_func lookup, nis_error *stat) { struct cons *nl; enum ismem status = ISMEM_NO; ASSERT(RW_READ_HELD(&g_cache_lock)); for (nl = varp->recursive; nl != 0; nl = nl->prochain) { switch (do_ismember_2(princp, nl->nom, refname, lookup, stat)) { case ISMEM_YES: ASSERT(RW_READ_HELD(&g_cache_lock)); return (ISMEM_YES); case ISMEM_NO: break; default: status = ISMEM_DUNNO; break; } } ASSERT(RW_READ_HELD(&g_cache_lock)); return (status); } static void delete_recursive(g_varieties *varp) { free_namelist(&varp->recursive); } static void printf_recursive(const g_varieties *varp, const char *title) { if (varp->recursive == 0) { (void) printf(" No recursive %smembers\n", title); } else { (void) printf(" Recursive %smembers:\n", title); print_namelist(varp->recursive); } } static void printf_varieties(g_varieties *varp, const char *title) { printf_explicit(varp, title); printf_implicit(varp, title); printf_recursive(varp, title); } /* * g_entry: A transformed group entry, of the sort we put in the group cache. */ struct g_entry { NIS_HASH_ITEM hdata; /* Hash table info */ uint32_t tte; /* Time to expire */ g_varieties include; /* Semantics are "in(include) */ g_varieties exclude; /* AND NOT in(exclude)" */ nis_object *group_obj; /* Keep it around,since all the */ /* stuff above points into it */ int visiting; /* Recursion-detection for */ /* do_ismember_2(), q.v. */ }; typedef struct g_entry g_entry; static bool_t visited(g_entry *ge); static g_entry * insert_g_entry(g_cache_ptr gc, g_entry *ge) { ASSERT(RW_WRITE_HELD(&g_cache_lock)); return nis_insert_item((NIS_HASH_ITEM *)ge, &gc->ht) == 0 ? 0 : ge; } static g_entry * lookup_g_entry(g_cache_ptr gc, nis_name name) { ASSERT(RW_READ_HELD(&g_cache_lock) || (RW_WRITE_HELD(&g_cache_lock))); return (g_entry *) nis_find_item(name, &gc->ht); } static void free_g_entry(g_entry *el) { if (el != 0) { if (el->hdata.name != 0) { free(el->hdata.name); } delete_explicit(&el->include); delete_implicit(&el->include); delete_recursive(&el->include); delete_explicit(&el->exclude); delete_implicit(&el->exclude); delete_recursive(&el->exclude); if (el->group_obj != 0) { nis_destroy_object(el->group_obj); } free(el); } } static void remove_g_entry( g_cache_ptr gc, nis_name name) /* NIS name to remove */ { g_entry *el; ASSERT(RW_WRITE_HELD(&g_cache_lock)); if (0 != (el = (g_entry *) nis_remove_item(name, &gc->ht))) { free_g_entry(el); } } static void delete_g_entry(g_cache_ptr gc) { ASSERT(RW_WRITE_HELD(&g_cache_lock)); nis_flush_table(&gc->ht, (nis_flush_func)free_g_entry); } /* * transform_group() -- fetch a group, massage it into the form it should have * had in the first place, and return a pointer to it. */ static g_entry * transform_group( nis_name gname, nis_object *gobj, /* Assumed to be a NIS_GROUP_OBJ */ nis_error *stat) { g_entry *ge; struct timeval tv; int nm; int i; nis_name *ml; ge = calloc(1, sizeof (*ge)); if (ge == 0) { syslog(LOG_WARNING, "nislib:transform_group() out of memory"); *stat = NIS_NOMEMORY; return (0); } ge->hdata.name = strdup(gname); /* ==== is strdup necessary? */ ge->group_obj = gobj; /* The calloc() set ge->visiting to zero for us */ nm = gobj->GR_data.gr_members.gr_members_len; ml = gobj->GR_data.gr_members.gr_members_val; for (i = 0; i < nm; i++) { g_varieties *var; nis_name t; bool_t ok = TRUE; var = &ge->include; t = ml[i]; if (*t == '-') { var = &ge->exclude; t++; } if (*t == '*') { ok = insert_implicit(var, t+2); } else if (*t == '@') { ok = insert_recursive(var, t+1); /* === could check to see whether someone specified */ /* it twice (or specified it in both 'include' */ /* and 'exclude'), but probably not worth it. */ } else { ok = insert_explicit(var, t); } if (!ok) { free_g_entry(ge); *stat = NIS_NOMEMORY; syslog(LOG_WARNING, "nislib:transform_group() insert failed, maybe out of memory"); return (0); } } (void) gettimeofday(&tv, (struct timezone *)0); ge->tte = (uint32_t)tv.tv_sec + gobj->zo_ttl; return (ge); } /* * cached_group_entry(groupnam, refname, lookup) * * Returns a pointer to an entry in group_cache for the group called * [group]. Adds the group to the cache if it isn't there already. * Also checks the time-to-expire and refreshes if necessary. * Returns NULL only if the universe is really broken. */ static g_entry * cached_group_entry( nis_name group, nis_name refname, nis_lookup_func lookup, nis_error *stat) { g_entry *ge; /* Group entry from cache */ g_cache_ptr gc; *stat = NIS_SUCCESS; /* always come in with a READ LOCK and exit with one too */ ASSERT(RW_READ_HELD(&g_cache_lock)); gc = get_g_cache(); if (gc == 0) { *stat = NIS_NOMEMORY; return (0); } ge = lookup_g_entry(gc, group); if (ge != 0 && !visited(ge)) { struct timeval tv; /* Expire the group if necessary */ (void) gettimeofday(&tv, (struct timezone *)0); if (ge->tte < tv.tv_sec) { (void) rw_unlock(&g_cache_lock); (void) rw_wrlock(&g_cache_lock); remove_g_entry(gc, group); ge = 0; } } if (ge != 0) { (void) rw_unlock(&g_cache_lock); (void) rw_wrlock(&g_cache_lock); gc->nhits++; gc->ncalls++; (void) rw_unlock(&g_cache_lock); (void) rw_rdlock(&g_cache_lock); return (ge); } (void) rw_unlock(&g_cache_lock); /* write lock the cache and try again */ (void) rw_wrlock(&g_cache_lock); ge = lookup_g_entry(gc, group); if (ge != 0 && !visited(ge)) { struct timeval tv; /* Expire the group if necessary */ (void) gettimeofday(&tv, (struct timezone *)0); if (ge->tte < tv.tv_sec) { remove_g_entry(gc, group); ge = 0; } } if (ge != 0) { gc->nhits++; gc->ncalls++; (void) rw_unlock(&g_cache_lock); (void) rw_rdlock(&g_cache_lock); return (ge); } else { nis_object *obj; gc->nmisses++; obj = get_group(group, refname, lookup, stat); if (obj == 0) { gc->ncalls++; (void) rw_unlock(&g_cache_lock); (void) rw_rdlock(&g_cache_lock); return (0); /* Couldn't read it */ } ge = transform_group(group, obj, stat); if (ge == 0) { gc->ncalls++; (void) rw_unlock(&g_cache_lock); nis_destroy_object(obj); (void) rw_rdlock(&g_cache_lock); return (0); } if (insert_g_entry(gc, ge) == 0) { gc->ncalls++; (void) rw_unlock(&g_cache_lock); free_g_entry(ge); (void) rw_rdlock(&g_cache_lock); *stat = NIS_NOMEMORY; return (0); } gc->ncalls++; (void) rw_unlock(&g_cache_lock); (void) rw_rdlock(&g_cache_lock); return (ge); } /* NOTREACHED */ } static pthread_key_t visit_log_key = PTHREAD_ONCE_KEY_NP; struct visit_log { g_entry *ge_id; struct visit_log *next; }; static struct visit_log *visit_list_main; static void mark_visit(g_entry *ge) { struct visit_log *v; v = calloc(1, sizeof (struct visit_log)); v->ge_id = ge; if (thr_main()) { v->next = visit_list_main; visit_list_main = v; } else { v->next = thr_get_storage(&visit_log_key, 0, NULL); (void) pthread_setspecific(visit_log_key, v); } } static void unmark_fatal(void) { (void) printf("unmark: fatal error\n"); abort(); } static void unmark_visit(g_entry *ge) { struct visit_log *v; if (thr_main()) { v = visit_list_main; if (v == NULL || v->ge_id != ge) /* must be in LIFO order */ unmark_fatal(); visit_list_main = v->next; } else { v = thr_get_storage(&visit_log_key, 0, NULL); if (v == NULL || v->ge_id != ge) /* must be in LIFO order */ unmark_fatal(); (void) pthread_setspecific(visit_log_key, v->next); } free(v); } static bool_t visited(g_entry *ge) { struct visit_log *v = thr_main()? visit_list_main : thr_get_storage(&visit_log_key, 0, NULL); while (v) { if (v->ge_id == ge) return (TRUE); v = v->next; } return (FALSE); } /* * The main machinery for testing "is A a member of B?". The work gets done in * do_ismember_2(), but clients won't normally call it directly; they use * __do_ismember() or nis_ismember(). */ static enum ismem do_ismember_2( nis_name princp, /* Principal name */ nis_name group, /* NIS group name */ nis_name refname, /* Group that's recursively using this one */ nis_lookup_func lookup, nis_error *stat) { g_entry *ge; int easy_include; enum ismem answer; /* * return YES if * in(princp, ge->include) AND NOT in(princp, ge->exclude) * return NO if * in(princp, ge->exclude) OR NOT in(princp, ge->include) * return DUNNO if * we can't look at some group. * * Checking for explicit members should be cheap, implicit members * should be fairly cheap, and recursive members may be expensive; * we try to order the tests accordingly. */ /* always enter with a READ LOCK and exit with one too */ ASSERT(RW_READ_HELD(&g_cache_lock)); ge = cached_group_entry(group, refname, lookup, stat); if (ge == 0) { /* === Should discover whether get_group() got NOTFOUND */ /* and, if so, return a definite ISMEM_NO (?) */ ASSERT(RW_READ_HELD(&g_cache_lock)); return (ISMEM_DUNNO); } if (lookup_explicit(&ge->exclude, princp) || lookup_implicit(&ge->exclude, princp)) { ASSERT(RW_READ_HELD(&g_cache_lock)); return (ISMEM_NO); } easy_include = lookup_explicit(&ge->include, princp) || lookup_implicit(&ge->include, princp); /* Probable optimization; result will be the same with or without */ if (easy_include == 0 && ge->include.recursive == 0) { ASSERT(RW_READ_HELD(&g_cache_lock)); return (ISMEM_NO); } if (visited(ge)) { ASSERT(RW_READ_HELD(&g_cache_lock)); return (ISMEM_DUNNO); } mark_visit(ge); switch (lookup_recursive(&ge->exclude, princp, group, lookup, stat)) { case ISMEM_YES: answer = ISMEM_NO; break; case ISMEM_NO: if (easy_include) { answer = ISMEM_YES; } else { answer = lookup_recursive(&ge->include, princp, group, lookup, stat); } break; default: if (!easy_include && (lookup_recursive(&ge->include, princp, group, lookup, stat) == ISMEM_NO)) { answer = ISMEM_NO; } else { answer = ISMEM_DUNNO; } } unmark_visit(ge); ASSERT(RW_READ_HELD(&g_cache_lock)); return (answer); } /* * nis_ismember(princp, group) * * This is the client function. It is a wrapper around an internal * interface that is used by both the clients and servers ofNIS * namespaces. */ bool_t nis_ismember( nis_name princp, /* Principal name */ nis_name group) /* NIS group name */ { bool_t ret; nis_error x; (void) rw_rdlock(&g_cache_lock); /* Err on the side of security: in case of doubt, return FALSE */ ret = (do_ismember_2(princp, group, 0, nis_lookup, &x) == ISMEM_YES); (void) rw_unlock(&g_cache_lock); return (ret); } /* * __do_ismember(princp, obj, lookup) * * Same as nis_ismember(), but calls an arbitrary lookup function rather than * nis_lookup(). Clearly nis_ismember() could call this, but just this * once let's save the extra function call. */ bool_t __do_ismember( nis_name princp, /* Principal name */ nis_object *obj, nis_lookup_func lookup) { nis_error stat; enum ismem isit; (void) rw_rdlock(&g_cache_lock); /* Err on the side of security: in case of doubt, return FALSE */ isit = do_ismember_2(princp, obj->zo_group, 0, lookup, &stat); (void) rw_unlock(&g_cache_lock); if (isit == ISMEM_DUNNO) { if (stat != NIS_SUCCESS) { syslog(LOG_ERR, "lookup failure on group \"%s\" from object \"%s.%s\"", obj->zo_group, obj->zo_name, obj->zo_domain); } } return (isit == ISMEM_YES); } void nis_print_group_entry( nis_name group) /* Name of the group to print */ { g_entry *ge; nis_error stat; (void) rw_rdlock(&g_cache_lock); ge = cached_group_entry(group, (nis_name)0, nis_lookup, &stat); if (ge == 0) { (void) printf("Could not find group \"%s\".\n", group); (void) rw_unlock(&g_cache_lock); return; } (void) printf("Group entry for \"%s\" group:\n", ge->hdata.name); printf_varieties(&ge->include, ""); printf_varieties(&ge->exclude, "non"); (void) rw_unlock(&g_cache_lock); } /* * nis_flushgroups() * * This function will free all memory associated with the group cache. */ void nis_flushgroups(void) { (void) rw_wrlock(&g_cache_lock); if (groups_cache != 0) { delete_g_entry(groups_cache); } (void) rw_unlock(&g_cache_lock); /* Else there's no cache, so no flushing to do */ } /* * nis_flushgroup(groupname) -- means "I've probably changed this group; flush * any group_cache info that depends on it". With the old group-cache * semantics, any group that included this one (directly or recursively) * would have to be flushed, so it was easiest just to do a complete * nis_flushgroups(); with the group-cache semantics here (i.e. information * about recursive members isn't propagated), we only have to flush the one * group. */ void __nis_flush_one_group(nis_name groupname) /* === new kinda public interface */ { (void) rw_wrlock(&g_cache_lock); if (groups_cache != 0) { remove_g_entry(groups_cache, groupname); } (void) rw_unlock(&g_cache_lock); /* Else there's no cache, so no flushing to do */ } /* * Same as __nis_flush_one_group() except that it accepts expanded group * names i.e. with embedded "groups_dir" as part of the name. */ void __nis_flush_group_exp_name(nis_name groupname) { char *domainname; domainname = nis_domain_of(groupname); if (strncmp(domainname, "groups_dir.", strlen("groups_dir.")) == 0) { /* Strip off "groups_dir" part of it */ char tname[NIS_MAXNAMELEN]; char buf[NIS_MAXNAMELEN]; (void) snprintf(tname, sizeof (tname), "%s.%s", nis_leaf_of_r(groupname, buf, NIS_MAXNAMELEN), nis_domain_of(domainname)); __nis_flush_one_group(tname); } else { __nis_flush_one_group(groupname); } } nis_name __nis_splice_name_r(const nis_name name, const char *splice, char *buf, size_t bufsize) { size_t newsize = strlen(name) + strlen(splice) + 2; char *p = buf; if (bufsize < newsize) { return (0); } (void) nis_leaf_of_r(name, p, bufsize); p += strlen(p); *p++ = '.'; (void) strcpy(p, splice); p += strlen(p); *p++ = '.'; (void) strcpy(p, nis_domain_of(name)); return (buf); } nis_name __nis_map_group_r( const nis_name name, char *buf, size_t bufsize) { return (__nis_splice_name_r(name, "groups_dir", buf, bufsize)); } /* * get_group() * * This function is a wrapper around the NIS code to fetch the group * objects. The object returned is not static; it has been malloc()ed. */ static nis_object * get_group( nis_name name, /* group name */ nis_name refname, /* group that referenced this group */ nis_lookup_func lookup, nis_error *stat) /* error status. */ { nis_result *res; nis_name gname; nis_object *obj; char namebuf[NIS_MAXNAMELEN]; gname = __nis_map_group_r(name, namebuf, sizeof (namebuf)); res = (*lookup)(gname, FOLLOW_LINKS + NO_AUTHINFO); if (res->status == NIS_NOTFOUND) { /* Finally, the reason for passing that 'refname' parameter */ /* all over creation: printing a worthwhile error message */ if (refname) syslog(LOG_ERR, "nislib:get_group() group object \"%s\", referenced by \"%s\", does not exist.", name, refname); else syslog(LOG_ERR, "nislib:get_group() group object \"%s\" does not exist.", name); *stat = res->status; nis_freeresult(res); return (0); } else if (res->status != NIS_SUCCESS) { if (refname) syslog(LOG_ERR, "nislib:get_group() object \"%s\", referenced by \"%s\", lookup failed.", name, refname); else syslog(LOG_ERR, "nislib:get_group() object \"%s\" lookup failed.", name); nis_lerror(res->status, "nislib:get_group reason"); *stat = res->status; nis_freeresult(res); return (0); } if (__type_of(NIS_RES_OBJECT(res)) != NIS_GROUP_OBJ) { if (refname) syslog(LOG_ERR, "nislib:get_group() object \"%s\", referenced by \"%s\", is not a group.", name, refname); else syslog(LOG_ERR, "nislib:get_group() object \"%s\" is not a group.", name); nis_freeresult(res); *stat = NIS_BADOBJECT; return (0); } /* * Steal the object before we free the whole result (cheaper than * cloning). ==== This will leak if ever (NIS_RES_NUMOBJ(res) > 1). */ obj = NIS_RES_OBJECT(res); NIS_RES_OBJECT(res) = 0; NIS_RES_NUMOBJ(res) = 0; *stat = res->status; nis_freeresult(res); return (obj); } /* * nis_addmember(princp, group) */ nis_error nis_addmember( nis_name princp, /* Principal name */ nis_name group) /* NIS group name */ { nis_result *res; /* the group in question */ nis_object *obj; /* The group object */ nis_error result; /* our result */ int nm, /* Number of members */ i; nis_name *ml; /* member list */ nis_object ngrp; /* New group object */ char name[NIS_MAXNAMELEN]; /* Group name */ /* Read the group object. */ obj = get_group(group, 0, nis_lookup, &result); if (obj == 0) { return (result); } nm = obj->GR_data.gr_members.gr_members_len; ml = obj->GR_data.gr_members.gr_members_val; for (i = 0; i < nm; i++) { if (nis_dir_cmp(princp, ml[i]) == SAME_NAME) { nis_destroy_object(obj); return (NIS_NAMEEXISTS); } } __nis_flush_one_group(group); ngrp = *obj; /* copy the object */ ngrp.GR_data.gr_members.gr_members_val = malloc((nm+1) * sizeof (nis_name)); if (!ngrp.GR_data.gr_members.gr_members_val) { syslog(LOG_ERR, "nis_addmember: Out of memory"); nis_destroy_object(obj); return (NIS_NOMEMORY); } for (i = 0; i < nm; i++) { ngrp.GR_data.gr_members.gr_members_val[i] = ml[i]; } ngrp.GR_data.gr_members.gr_members_val[nm] = princp; ngrp.GR_data.gr_members.gr_members_len = nm+1; (void) snprintf(name, sizeof (name), "%s.%s", obj->zo_name, obj->zo_domain); /* XXX overwrite problem if multiple writers ? */ res = nis_modify(name, &ngrp); free(ngrp.GR_data.gr_members.gr_members_val); result = res->status; nis_freeresult(res); nis_destroy_object(obj); return (result); } /* * nis_removemember(princp, group) */ nis_error nis_removemember( nis_name princp, /* Principal name */ nis_name group) /* NIS group name */ { nis_result *res; /* the group in question */ nis_object *obj; /* The group object */ nis_error result; /* our result */ int nm, /* Number of members */ i, x; nis_name *ml; /* member list */ nis_object ngrp; /* New group object */ char name[NIS_MAXNAMELEN]; /* Group name */ obj = get_group(group, 0, nis_lookup, &result); if (!obj) return (result); nm = obj->GR_data.gr_members.gr_members_len; ml = obj->GR_data.gr_members.gr_members_val; for (i = 0; i < nm; i++) { if (nis_dir_cmp(princp, ml[i]) == SAME_NAME) break; } /* If i == nm then we didn't find the member to remove */ if (i == nm) { nis_destroy_object(obj); return (NIS_NOSUCHNAME); } __nis_flush_one_group(group); ngrp = *obj; /* copy the object */ ngrp.GR_data.gr_members.gr_members_val = malloc(nm * sizeof (nis_name)); if (!ngrp.GR_data.gr_members.gr_members_val) { syslog(LOG_ERR, "nis_addmember: Out of memory"); nis_destroy_object(obj); return (NIS_NOMEMORY); } /* * We know that ml[0..i-1] aren't the same name, and ml[i] is, so * copy the former, skip the latter, and then check ml[i+1..nm-1] */ for (x = 0; x < i; x++) { ngrp.GR_data.gr_members.gr_members_val[x] = ml[x]; } /* Note: Removes _all_ instances of a principal name */ while (++i < nm) { if (nis_dir_cmp(princp, ml[i]) != SAME_NAME) { ngrp.GR_data.gr_members.gr_members_val[x] = ml[i]; ++x; } } ngrp.GR_data.gr_members.gr_members_len = x; (void) snprintf(name, sizeof (name), "%s.%s", obj->zo_name, obj->zo_domain); res = nis_modify(name, &ngrp); free(ngrp.GR_data.gr_members.gr_members_val); result = res->status; nis_freeresult(res); nis_destroy_object(obj); return (result); } /* * nis_verifygroup(group) * * Verify the existence of the named group. * */ nis_error nis_verifygroup(nis_name group) /* NIS group name */ { nis_name grpname; nis_result *res; nis_error result; char namebuf[NIS_MAXNAMELEN]; grpname = __nis_map_group_r(group, namebuf, sizeof (namebuf)); res = nis_lookup(grpname, FOLLOW_LINKS); if ((res->status == NIS_SUCCESS) || (res->status == NIS_S_SUCCESS)) { if (__type_of(res->objects.objects_val) == NIS_GROUP_OBJ) result = NIS_SUCCESS; else result = NIS_BADOBJECT; } else result = res->status; nis_freeresult(res); return (result); } /* * __nis_creategroup_obj(name, flags, obj) * * This function creates an empty group of the given name. * Using obj for setting the group object defaults. */ nis_error __nis_creategroup_obj(nis_name name, uint_t flags, nis_object *obj) { nis_object grpobj; group_obj *grdata; nis_name grpname; nis_error result; nis_result *res; char namebuf[NIS_MAXNAMELEN]; char leafbuf[NIS_MAXSTRINGLEN]; grpname = __nis_map_group_r(name, namebuf, sizeof (namebuf)); grpobj.zo_data.zo_type = NIS_GROUP_OBJ; grdata = &(grpobj.GR_data); /* Tease apart the name we just created in nis_map_group_r(); */ /* ==== maybe we need a more sensible interface. */ grpobj.zo_name = nis_leaf_of_r(grpname, leafbuf, sizeof (leafbuf)); grpobj.zo_domain = nis_domain_of(grpname); if (obj) { grpobj.zo_owner = obj->zo_owner; grpobj.zo_group = obj->zo_group; grpobj.zo_access = obj->zo_access; grpobj.zo_ttl = obj->zo_ttl; } else { grpobj.zo_owner = nis_local_principal(); grpobj.zo_group = nis_local_group(); grpobj.zo_access = DEFAULT_RIGHTS; grpobj.zo_ttl = 3600; /* one hour by default */ } grdata->gr_flags = flags; grdata->gr_members.gr_members_len = 0; grdata->gr_members.gr_members_val = 0; res = nis_add(grpname, &grpobj); result = res->status; nis_freeresult(res); /* If we're just creating it then it shouldn't have been in the */ /* cache so this is a no-op, but let's play safe. */ __nis_flush_one_group(name); return (result); } /* * nis_creategroup(name, flags) * * This function creates an empty group of the given name. */ nis_error nis_creategroup(nis_name name, uint_t flags) { return (__nis_creategroup_obj(name, flags, NULL)); } nis_error nis_destroygroup(nis_name name) { nis_name grpname; nis_result *res; nis_error result; char namebuf[NIS_MAXNAMELEN]; grpname = __nis_map_group_r(name, namebuf, sizeof (namebuf)); res = nis_remove(grpname, (nis_object *)0); result = res->status; nis_freeresult(res); __nis_flush_one_group(name); return (result); }