summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Gerdts <mike.gerdts@joyent.com>2019-03-31 00:04:51 +0000
committerTrent Mick <trentm@gmail.com>2019-03-31 00:04:51 +0000
commit359baa5748ddabe6a4421f75e33c4d044190734d (patch)
tree86d4f8bdfe0ef0d38d200d7597574a16aa2ef2fb
parent486e689913103fa372f7e2c3e054b2bd602546c4 (diff)
downloadillumos-joyent-cr6083-OS-7708.tar.gz
OS-7708 nss_ldap needs to cache netgroups Reviewed by: Robert Mustacchi <rm@joyent.com> Reviewed by: Jason King <jason.king@joyent.com>cr6083-OS-7708
-rw-r--r--usr/src/cmd/initpkg/nscd.conf9
-rw-r--r--usr/src/lib/nsswitch/Makefile.com8
-rw-r--r--usr/src/lib/nsswitch/Makefile.targ7
-rw-r--r--usr/src/lib/nsswitch/compat/Makefile.com5
-rw-r--r--usr/src/lib/nsswitch/dns/Makefile.com4
-rw-r--r--usr/src/lib/nsswitch/files/Makefile.com4
-rw-r--r--usr/src/lib/nsswitch/ldap/Makefile4
-rw-r--r--usr/src/lib/nsswitch/ldap/Makefile.com8
-rw-r--r--usr/src/lib/nsswitch/ldap/Makefile.targ20
-rw-r--r--usr/src/lib/nsswitch/ldap/amd64/Makefile5
-rw-r--r--usr/src/lib/nsswitch/ldap/common/getexecattr.c5
-rw-r--r--usr/src/lib/nsswitch/ldap/common/getgrent.c6
-rw-r--r--usr/src/lib/nsswitch/ldap/common/getnetgrent.c2016
-rw-r--r--usr/src/lib/nsswitch/ldap/common/getspent.c7
-rw-r--r--usr/src/lib/nsswitch/ldap/common/ldap_common.h4
-rw-r--r--usr/src/lib/nsswitch/ldap/common/provider.d50
-rw-r--r--usr/src/lib/nsswitch/ldap/i386/Makefile6
-rw-r--r--usr/src/lib/nsswitch/ldap/sparc/Makefile6
-rw-r--r--usr/src/lib/nsswitch/ldap/sparcv9/Makefile6
-rw-r--r--usr/src/lib/nsswitch/nis/Makefile.com5
-rw-r--r--usr/src/man/man4/nscd.conf.47
-rw-r--r--usr/src/test/Makefile2
-rw-r--r--usr/src/test/nsswitch-tests/Makefile20
-rw-r--r--usr/src/test/nsswitch-tests/runfiles/Makefile41
-rw-r--r--usr/src/test/nsswitch-tests/runfiles/default.run26
-rw-r--r--usr/src/test/nsswitch-tests/tests/Makefile20
-rw-r--r--usr/src/test/nsswitch-tests/tests/ldap/Makefile75
-rw-r--r--usr/src/test/nsswitch-tests/tests/ldap/generalized_time.c241
28 files changed, 2212 insertions, 405 deletions
diff --git a/usr/src/cmd/initpkg/nscd.conf b/usr/src/cmd/initpkg/nscd.conf
index a2a06950ce..3ebb50bcd2 100644
--- a/usr/src/cmd/initpkg/nscd.conf
+++ b/usr/src/cmd/initpkg/nscd.conf
@@ -21,14 +21,13 @@
#
# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
-#
-#ident "%Z%%M% %I% %E% SMI"
+# Copyright 2019 Joyent, Inc.
#
#
# Currently supported cache names:
# audit_user, auth_attr, bootparams, ethers
-# exec_attr, group, hosts, ipnodes, netmasks
+# exec_attr, group, hosts, ipnodes, netgroup, netmasks
# networks, passwd, printers, prof_attr, project
# protocols, rpc, services, tnrhdb, tnrhtp, user_attr
#
@@ -77,6 +76,10 @@
keep-hot-count ipnodes 20
check-files ipnodes yes
+ positive-time-to-live netgroup 3600
+ negative-time-to-live netgroup 5
+ enable-cache netgroup yes
+
positive-time-to-live netmasks 3600
negative-time-to-live netmasks 5
keep-hot-count netmasks 20
diff --git a/usr/src/lib/nsswitch/Makefile.com b/usr/src/lib/nsswitch/Makefile.com
index 570ae3a035..df29c223a5 100644
--- a/usr/src/lib/nsswitch/Makefile.com
+++ b/usr/src/lib/nsswitch/Makefile.com
@@ -22,7 +22,7 @@
# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
-# Copyright (c) 2018, Joyent, Inc.
+# Copyright 2019 Joyent, Inc.
include $(SRC)/lib/Makefile.lib
@@ -36,12 +36,6 @@ LINTOUT = lint.out
CPPFLAGS += -D_REENTRANT
-CERRWARN += -_gcc=-Wno-switch
-CERRWARN += -_gcc=-Wno-uninitialized
-CERRWARN += -_gcc=-Wno-parentheses
-CERRWARN += -_gcc=-Wno-unused-variable
-CERRWARN += -_gcc=-Wno-address
-
# not linted
SMATCH=off
diff --git a/usr/src/lib/nsswitch/Makefile.targ b/usr/src/lib/nsswitch/Makefile.targ
index 399d9be82c..d1fc6976c4 100644
--- a/usr/src/lib/nsswitch/Makefile.targ
+++ b/usr/src/lib/nsswitch/Makefile.targ
@@ -21,15 +21,14 @@
#
# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
-#
-# ident "%Z%%M% %I% %E% SMI"
+# Copyright 2019 Joyent, Inc.
#
all: $(LIBS)
# include global library targets.
include $(SRC)/lib/Makefile.targ
-
+include $(SRC)/lib/Makefile.usdt
$(ROOT32DYNLIB) := FILEMODE= 755
$(ROOT64DYNLIB) := FILEMODE= 755
@@ -39,7 +38,7 @@ $(DYNLIB1) := CTFMERGE_POST = $(CTFMERGE_LIB)
# DYNLIB1 is used rather than DYNLIB to prevent the automagic expansion and
# creation of a libXXXX.so$(VERS) target.
-$(DYNLIB1): pics .WAIT $$(PICS)
+$(DYNLIB1): pics .WAIT $$(PICS) .WAIT $(USDT_PICS)
$(BUILD.SO)
$(POST_PROCESS_SO)
diff --git a/usr/src/lib/nsswitch/compat/Makefile.com b/usr/src/lib/nsswitch/compat/Makefile.com
index 5b600c5588..c78d532734 100644
--- a/usr/src/lib/nsswitch/compat/Makefile.com
+++ b/usr/src/lib/nsswitch/compat/Makefile.com
@@ -22,8 +22,7 @@
#
# Copyright 1993,2001-2003 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
-#
-# ident "%Z%%M% %I% %E% SMI"
+# Copyright 2019 Joyent, Inc.
#
# lib/nsswitch/compat/Makefile.com
@@ -37,6 +36,8 @@ OBJECTS = getpwent.o \
getuserattr.o \
getauuser.o
+pics/compat_common.o := CERRWARN += -_gcc=-Wno-uninitialized
+
# include common nsswitch library definitions.
include ../../Makefile.com
diff --git a/usr/src/lib/nsswitch/dns/Makefile.com b/usr/src/lib/nsswitch/dns/Makefile.com
index 0366633c0c..ceff67bced 100644
--- a/usr/src/lib/nsswitch/dns/Makefile.com
+++ b/usr/src/lib/nsswitch/dns/Makefile.com
@@ -21,6 +21,7 @@
#
# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
+# Copyright 2019 Joyent, Inc.
#
# lib/nsswitch/dns/Makefile.com
@@ -44,5 +45,8 @@ CPPFLAGS += -DNSS_DNS_LIBRESOLV=\"libresolv.so.2\"
LINTFLAGS += -erroff=E_GLOBAL_COULD_BE_STATIC2
+pics/dns_common.o := CERRWARN += -_gcc=-Wno-uninitialized
+pics/gethostent6.o := CERRWARN += -_gcc=-Wno-uninitialized
+
LDLIBS += -lnsl -lresolv_joy -lsocket
DYNLIB1 = nss_dns.so$(VERS)
diff --git a/usr/src/lib/nsswitch/files/Makefile.com b/usr/src/lib/nsswitch/files/Makefile.com
index 1489badee6..86cd43b071 100644
--- a/usr/src/lib/nsswitch/files/Makefile.com
+++ b/usr/src/lib/nsswitch/files/Makefile.com
@@ -21,6 +21,7 @@
#
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
+# Copyright 2019 Joyent, Inc.
#
LIBRARY = libnss_files.a
@@ -59,6 +60,9 @@ CPPFLAGS += -I../../../common/inc
LINTFLAGS += -erroff=E_GLOBAL_COULD_BE_STATIC2
LINTFLAGS64 += -erroff=E_GLOBAL_COULD_BE_STATIC2
+pics/gethostent.o := CERRWARN += -_gcc=-Wno-switch
+pics/gethostent.o := CERRWARN += -_gcc=-Wno-uninitialized
+
LDLIBS += -lnsl
DYNLIB1 = nss_files.so$(VERS)
diff --git a/usr/src/lib/nsswitch/ldap/Makefile b/usr/src/lib/nsswitch/ldap/Makefile
index eabc270b02..835e2945a3 100644
--- a/usr/src/lib/nsswitch/ldap/Makefile
+++ b/usr/src/lib/nsswitch/ldap/Makefile
@@ -19,11 +19,9 @@
#
# CDDL HEADER END
#
-#
-#ident "%Z%%M% %I% %E% SMI"
-#
# Copyright (c) 1999 by Sun Microsystems, Inc.
# All rights reserved.
+# Copyright 2019 Joyent, Inc.
#
# lib/nsswitch/ldap/Makefile
#
diff --git a/usr/src/lib/nsswitch/ldap/Makefile.com b/usr/src/lib/nsswitch/ldap/Makefile.com
index 694bd5f023..0abf9bd4c9 100644
--- a/usr/src/lib/nsswitch/ldap/Makefile.com
+++ b/usr/src/lib/nsswitch/ldap/Makefile.com
@@ -21,6 +21,7 @@
#
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
+# Copyright 2019 Joyent, Inc.
#
LIBRARY = libnss_ldap.a
@@ -50,13 +51,16 @@ OBJECTS = getauthattr.o \
tsol_getrhent.o \
tsol_gettpent.o \
ldap_common.o \
- ldap_utils.o
+ ldap_utils.o \
+ list.o
+
+USDT_PROVIDERS = provider.d
# include common nsswitch library definitions.
include ../../Makefile.com
CPPFLAGS += -I../../../libsldap/common
-LDLIBS += -lsldap -lnsl -lldap
+LDLIBS += -lsldap -lnsl -lldap -lavl -lscf
LINTFLAGS += -erroff=E_GLOBAL_COULD_BE_STATIC2
LINTFLAGS64 += -erroff=E_GLOBAL_COULD_BE_STATIC2
DYNLIB1 = nss_ldap.so$(VERS)
diff --git a/usr/src/lib/nsswitch/ldap/Makefile.targ b/usr/src/lib/nsswitch/ldap/Makefile.targ
new file mode 100644
index 0000000000..2352c0892e
--- /dev/null
+++ b/usr/src/lib/nsswitch/ldap/Makefile.targ
@@ -0,0 +1,20 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019 Joyent, Inc.
+#
+
+include $(SRC)/lib/nsswitch/Makefile.targ
+
+pics/%.o: $(SRC)/common/list/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
diff --git a/usr/src/lib/nsswitch/ldap/amd64/Makefile b/usr/src/lib/nsswitch/ldap/amd64/Makefile
index 1334471b25..ef375387db 100644
--- a/usr/src/lib/nsswitch/ldap/amd64/Makefile
+++ b/usr/src/lib/nsswitch/ldap/amd64/Makefile
@@ -22,8 +22,7 @@
#
# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
-#
-# ident "%Z%%M% %I% %E% SMI"
+# Copyright 2019 Joyent, Inc.
#
include ../Makefile.com
@@ -31,6 +30,6 @@ include $(SRC)/lib/Makefile.lib.64
LIBS = $(DYNLIB1)
-include ../../Makefile.targ
+include ../Makefile.targ
install: all $(ROOT64DYNLIB)
diff --git a/usr/src/lib/nsswitch/ldap/common/getexecattr.c b/usr/src/lib/nsswitch/ldap/common/getexecattr.c
index abd22908e0..8ec4197428 100644
--- a/usr/src/lib/nsswitch/ldap/common/getexecattr.c
+++ b/usr/src/lib/nsswitch/ldap/common/getexecattr.c
@@ -21,6 +21,7 @@
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
+ * Copyright 2019 Joyent, Inc.
*/
#include <secdb.h>
@@ -421,7 +422,7 @@ get_wild(ldap_backend_ptr be, nss_XbyY_args_t *argp, int getby_flag)
const char *type = _priv_exec->type;
if (strpbrk(policy, "*()\\") != NULL ||
- type != NULL && strpbrk(type, "*()\\") != NULL)
+ (type != NULL && strpbrk(type, "*()\\") != NULL))
return ((nss_status_t)NSS_NOTFOUND);
if (_priv_exec->id != NULL)
@@ -545,7 +546,7 @@ getbynam(ldap_backend_ptr be, void *a)
const char *type = _priv_exec->type;
if (strpbrk(policy, "*()\\") != NULL ||
- type != NULL && strpbrk(type, "*()\\") != NULL ||
+ (type != NULL && strpbrk(type, "*()\\") != NULL) ||
_ldap_filter_name(name, _priv_exec->name, sizeof (name)) != 0)
return ((nss_status_t)NSS_NOTFOUND);
ret = snprintf(searchfilter, sizeof (searchfilter),
diff --git a/usr/src/lib/nsswitch/ldap/common/getgrent.c b/usr/src/lib/nsswitch/ldap/common/getgrent.c
index 291d16dbc6..bbfa049bcd 100644
--- a/usr/src/lib/nsswitch/ldap/common/getgrent.c
+++ b/usr/src/lib/nsswitch/ldap/common/getgrent.c
@@ -23,6 +23,7 @@
* Use is subject to license terms.
*
* Copyright 2017 Nexenta Systems, Inc. All rights reserved.
+ * Copyright 2019 Joyent, Inc.
*/
#include <grp.h>
@@ -161,7 +162,7 @@ _nss_ldap_group2str(ldap_backend_ptr be, nss_XbyY_args_t *argp)
* If we find an '=' in the member attribute value, treat it as
* a DN, otherwise as a username.
*/
- if (member_str = strchr(members->attrvalue[i], '=')) {
+ if ((member_str = strchr(members->attrvalue[i], '=')) != NULL) {
member_str++; /* skip over the '=' */
/* Fail if we can't pull a username out of the RDN */
if (! (member_str = strtok_r(member_str,
@@ -367,7 +368,8 @@ getbymember(ldap_backend_ptr be, void *a)
* value, treat it as a DN, otherwise as a
* username.
*/
- if (member_str = strchr(membervalue[j], '=')) {
+ if ((member_str = strchr(membervalue[j], '=')) !=
+ NULL) {
member_str++; /* skip over the '=' */
member_str = strtok_r(member_str, ",",
&strtok_state);
diff --git a/usr/src/lib/nsswitch/ldap/common/getnetgrent.c b/usr/src/lib/nsswitch/ldap/common/getnetgrent.c
index c2ff7466ec..e832574ac2 100644
--- a/usr/src/lib/nsswitch/ldap/common/getnetgrent.c
+++ b/usr/src/lib/nsswitch/ldap/common/getnetgrent.c
@@ -21,18 +21,182 @@
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
+ * Copyright 2019 Joyent, Inc.
*/
+/*
+ * nss_ldap netgroup support
+ *
+ * Like other name service switch modules, this code may run in nscd or
+ * arbitrary processes that call getnetgrent(3C), getnetgrent_r(3C),
+ * setnetgrent(3C), endnetgrent(3C), and innetgr(3C). For a reason that is not
+ * entirely clear ("due to backend knowledge"), libc's nss_pack() instructs
+ * callers of all netgroup functions other than innetgr(3C) to NSS_TRYLOCAL
+ * rather than allowing the query to be passed to nscd. This is probably of
+ * negligible impact, as modern use of netgroup lookups is almost exclusively
+ * through innetgr(3C).
+ *
+ * Whether being called from libc or nscd, initialization happens via a call to
+ * _nss_ldap_netgroup_constr(). There is no destructor - rather the library is
+ * closed with dlclose(), which will trigger ngc_fini(). The most likely
+ * consumer, nscd, restarts itself when it sees a configuration change.
+ *
+ *
+ * Netgroup Caching
+ *
+ * Caching of netgroups is done within this module because the type of caching
+ * that is most useful for netgroups is a poor fit for nscd. nscd is focused on
+ * caching individual results, with the assumption that one request doesn't
+ * perform a high cost operation from which a subsequent operation may benefit.
+ * The poster child for this problem is the most common case: innetgr(). The
+ * LDAP NIS schema is poorly designed for innetgr because it forces a full
+ * object transfer for every innetgr call. Native NIS avoids this with the
+ * revnetgroup map.
+ *
+ * Netgroups are cached only when running as part of the
+ * svc:/system/name-service-cache:default service. nscd.conf(4) can be used to
+ * override defaults with enable-cache, positive-time-to-live, and
+ * negative-time-to-live. While enable-cache defaults to "yes", lookups that
+ * bypass nscd still are not cached.
+ *
+ * The cache is implemented as an AVL tree (ngc_cache), with each node of the
+ * tree representing one netgroup. Nested netgroups are not flattened, implying
+ * that if there are two netgroups that each include a common third netgroup,
+ * there will be three tree nodes. Expired netgroups that still have references
+ * are migrated from ngc_cache (AVL tree) to ngc_graveyard (list).
+ *
+ * The following functions are intended to be called by those functions that
+ * implement setnetgrent(), endnetgrent(), and innetgr().
+ *
+ * netgroup_get() On success, a reference counted pointer to a cached
+ * netgroup is returned. A call to this function may
+ * trigger the netgroup to be loaded from LDAP.
+ * The context that is passed is used to ensure that
+ * use of memberNisNetgroup does not lead to infinite
+ * loops.
+ * netgroup_rele() Releases the reference returned by netgroup_get.
+ *
+ *
+ * Locking
+ *
+ * There is one big lock, ngc_lock. It must be held while accessing ngc_cache,
+ * or any of the lists that may reference netgroup_t objects. It must also be
+ * held while accessing the ng_refcnt field of any netgroup_t that is in any of
+ * the ngc_* lists or tree.
+ *
+ * ngc_*() functions operate on netgroup_t objects. ngc_*_locked() functions
+ * must be called with ngc_lock held and the others expect ngc_lock not to be
+ * held.
+ *
+ *
+ * Expiry
+ *
+ * Clearly, it is not OK to cache data forever without checking to ensure that
+ * it is current. When a netgroup is loaded from LDAP, its expiration time is
+ * set to ngc_pos_ttl (default NGC_POS_TTL) seconds from the time that it is
+ * loaded. Any queries that start after the expiration time will trigger the
+ * netgroup to be loaded from LDAP again. Negative (NSS_NOTFOUND) results are
+ * also cached for ngc_neg_ttl seconds. As mentioned above, the defaults can be
+ * overridden by positive-time-to-live and negative-time-to-live in nscd.conf.
+ *
+ * To avoid full reloads of netgroups on a regular basis, a netgroup_get() that
+ * occurs in the final 25% of a netgroup's expiry period will trigger a worker
+ * thread to make an LDAP query to check the modifyTimestamp of the relevant
+ * netgroup object. If the modifyTimestamp has not changed from its value
+ * stored in the cache, the netgroup's timeout is reset to ngc_pos_ttl seconds
+ * in the future. If the modifyTimestamp has changed to a later time, the
+ * netgroup will be reloaded and replaced in or removed from the cache. In
+ * addition to reducing the load on the LDAP server(s), this approach leads to
+ * lower latency for frequent lookups.
+ *
+ * Each netgroup in the cache is also in the ngc_pos_expire_queue or
+ * ngc_neg_expire_queue list_t. These lists are ordered by expiry time, with
+ * each new or renewed netgroup being placed at the end of the appropriate list.
+ * Whether triggered by a lookup or a periodic reap (see ngc_reap_locked()),
+ * when an expired netgroup is removed from ngc_cache, it is also removed from
+ * the expire queue. See ngc_dispose_locked().
+ *
+ * When setnetgrent() is called, it calls netgroup_get() and holds the reference
+ * in the context that lives until endnetgrent() is called or the netgroup is
+ * fully consumed. To avoid problems with a netgroup reference that is returned
+ * in the instant before the netgroup expires, the reference is valid for at
+ * least EXPIRE_SECONDS seconds.
+ *
+ * When nscd sees a configuration change, it restarts itself. Thus, there is no
+ * need for any finalization code to free state. Applications that call
+ * setnetgrent() should call endnetgrent() to free resources held by this module
+ * and libslap.
+ *
+ *
+ * Example
+ *
+ * In this example, the cache has 6 netgroups: admins, bastion, blah, devs, ops,
+ * and qa. In ngc_cache, the solid lines represent the tree structure, the
+ * dotted lines represent expiration queues (lists), and the hash lines
+ * represent the warm queue.
+ *
+ * - Five of them are not expired: admins, bastion, blah, devs, qa
+ * - Three of the non-expired netgroups exist: admins, bastion, devs
+ * - Two of these are active and near their expiration time so a refresh
+ * has been queued: (admins, bastion)
+ * - Two of the recently queried netgroups do not exist: blah, qa
+ * - One netgroup is expired but is still referenced: ops
+ *
+ * ngc_cache ngc_graveyard
+ * | |
+ * V V
+ * +----------+ +---------+
+ * | @blah | | @ops |
+ * | negative |<.............. | expired |
+ * +----------+ : +---------+
+ * / \ :
+ * +----------+ +----------+ :
+ * ....>| @bastion |....>| @devs | :
+ * : | positive | | positive | :
+ * : +----------+ +----------+ :
+ * : / ^ \ :
+ * +----------+ # +----------+
+ * | @admins |### | @qa |
+ * | positive |<########## | negative |
+ * +----------+ # +----------+
+ * ^ # ^
+ * : # :
+ * ngc_pos_expire_queue ngc_warm_queue ngc_neg_expire_queue
+ *
+ * When the thread that has a hold on ops releases it, ops will be removed from
+ * ngc_graveyard and will be freed.
+ *
+ * When ngc_warmer wakes up, it will walk far enough into each of
+ * ngc_neg_expire_queue and ngc_pos_expire_queue to see the first non-expired
+ * netgroup on each list or the end of the list. Those netgroups that it
+ * encounters that are expired will be moved to ngc_graveyard or freed,
+ * depending on whether they have references. Both admins and bastion will have
+ * their modifyTimestamp attribute queried, causing each of those netgroups to
+ * be renewed or expired and replaced.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <libscf.h>
+#include <locale.h>
+#include <stddef.h>
#include <syslog.h>
+#include <sys/avl.h>
+#include <sys/debug.h>
+#include <sys/list.h>
+#include <sys/sdt.h>
+#include <sys/sysmacros.h>
+#include <thread.h>
#include "ldap_common.h"
/* netgroup attributes filters */
#define _N_TRIPLE "nisnetgrouptriple"
#define _N_MEMBER "membernisnetgroup"
+#define _N_MODIFYSTAMP "modifyTimestamp"
-#define PRINT_VAL(a) (((a).argc == 0) || ((a).argv == NULL) || \
- ((a).argv[0] == NULL)) ? "*" : (a).argv[0]
+#define PRINT_VAL(a) ((((a).argc == 0) || ((a).argv == NULL) || \
+ ((a).argv[0] == NULL)) ? "*" : (a).argv[0])
#define ISNULL(a) (a == NULL ? "<NULL>" : a)
#define MAX_DOMAIN_LEN 1024
#define MAX_TRIPLE_LEN (MAXHOSTNAMELEN + LOGNAME_MAX + \
@@ -44,41 +208,1001 @@
#define N_HASH 257
#define COMMA ','
+/* These are in seconds. Be careful: ngc_expire and ng_refresh are in nsec. */
+#define NGC_POS_TTL 3600 /* or nscd.conf positive-time-to-live */
+#define NGC_NEG_TTL 5 /* or nscd.conf negative-time-to-live */
+
+#define NGC_DATESTR_LEN 24
+
+#define NSCD_FMRI "svc:/system/name-service-cache:default"
+#define NSCD_CONF "/etc/nscd.conf"
+
static const char *netgrent_attrs[] = {
_N_TRIPLE,
_N_MEMBER,
+ _N_MODIFYSTAMP,
(char *)NULL
};
+static const char *netgrent_stamp[] = {
+ _N_MODIFYSTAMP,
+ (char *)NULL
+};
+
+/*
+ * Each of these will reference strings in ng_result. Per split_triple(), NULL
+ * is treated as a wild card.
+ */
+typedef struct {
+ const char *ngt_host;
+ const char *ngt_user;
+ const char *ngt_domain;
+} ngc_triple_t;
+
+typedef enum {
+ NGC_FLAG_NEGATIVE = 0x01,
+ NGC_FLAG_INCACHE = 0x02,
+ NGC_FLAG_INWARMER = 0x04,
+ NGC_FLAG_INEXPQUEUE = 0x08,
+} ngc_flags_t;
+
+#define NGC_NEGATIVE(ng) (!!((ng)->ng_flags & NGC_FLAG_NEGATIVE))
+#define NGC_INCACHE(ng) (!!((ng)->ng_flags & NGC_FLAG_INCACHE))
+#define NGC_INWARMER(ng) (!!((ng)->ng_flags & NGC_FLAG_INWARMER))
+#define NGC_INEXPQUEUE(ng) (!!((ng)->ng_flags & NGC_FLAG_INEXPQUEUE))
+
+#define NGC_CLEAR(ng, flags) ((ng)->ng_flags &= ~(flags))
+#define NGC_SET(ng, flags) ((ng)->ng_flags |= (flags))
+
+/*
+ * A cached netgroup.
+ *
+ * Each netgroup_t is in ng_cache (with NGC_FLAG_INCACHE) or ng_graveyard
+ * (without NGC_FLAG_INCACHE). Those that are in ng_cache are also in
+ * ngc_pos_expire_queue or ngc_neg_expire_queue, roughly sorted by ng_expire
+ * (soonest at head). References in ng_cache, ng_graveyard,
+ * ng_pos_expire_queue, and ng_neg_expire_queue do not cause ng_refcnt to
+ * increase. Any other reference while not also holding ng_lock (including
+ * those for ng_warm_queue) do increase ng_refcnt.
+ *
+ * This structure is arranged such that it is as friendly as possible to dtrace
+ * scripts that need to work across 32-bit and 64-bit executables. In
+ * particular, pointers come late in the structure and any 64-bit fields are
+ * 64-bit aligned to avoid strange offsets.
+ */
+typedef struct {
+ /* Monotonic seconds */
+ uint32_t ng_birth; /* For cache debugging */
+ uint32_t ng_expire; /* Do not use after */
+ uint32_t ng_refresh; /* Time to update ahead of expiry */
+
+ uint32_t ng_refcnt;
+ uint64_t ng_lastchange; /* Tenths of second since epoch */
+ ngc_flags_t ng_flags;
+ ns_ldap_result_t *ng_result;
+ ngc_triple_t *ng_triples;
+ uint32_t ng_triplecnt;
+ union {
+ avl_node_t ng_cache; /* if NGC_INCACHE(), in ngc_cache */
+ list_node_t ng_tombstone; /* !NGC_INCACHE(), in ngc_graveyard */
+ } ng_linkage;
+ list_node_t ng_warm_linkage; /* ngc_warm_queue */
+ list_node_t ng_expire_linkage; /* ngc_{pos,neg}_expire_queue */
+ const char *ng_name; /* Will reference space after struct */
+} netgroup_t;
+
+/*
+ * While iterating a netgroup we must keep track of which memberNisNetgroups
+ * have been seen and avoid visiting the same nested netgroup multiple times.
+ * This is particularly important for netgroups that form circular references.
+ *
+ * This is handled with a netgroup_table_t. Each iterator establishes a
+ * netgroup_table_t containing a hash table of netgroup_name_t elements.
+ */
typedef struct netgroup_name {
- char *name;
- struct netgroup_name *next;
- struct netgroup_name *next_hash;
+ char *ngn_name;
+ struct netgroup_name *ngn_next;
+ struct netgroup_name *ngn_next_hash;
} netgroup_name_t;
typedef struct {
- netgroup_name_t *hash_list[N_HASH];
- netgroup_name_t *to_do;
- netgroup_name_t *done;
+ netgroup_name_t *ngt_hash_list[N_HASH];
+ netgroup_name_t *ngt_to_do;
+ netgroup_name_t *ngt_done;
} netgroup_table_t;
+typedef unsigned int hash_t;
+
+/*
+ * This cookie is used across setnetgrent()/getnetgrent()/endnetgrent().
+ */
typedef struct {
- ns_ldap_result_t *results;
- ns_ldap_entry_t *entry;
- char **attrs;
- char *netgroup;
- netgroup_table_t tab;
+ netgroup_t *gnc_netgroup; /* cached netgroup */
+ ns_ldap_entry_t *gnc_entry; /* entry in netgroup->ng_result */
+ char *gnc_name; /* netgroup name */
+ uint32_t gnc_curtriple; /* index in netgroup->ng_triples */
+ netgroup_table_t gnc_tab;
} getnetgrent_cookie_t;
-typedef struct {
- struct nss_innetgr_args *ia;
- const char *ssd_filter;
- const char *netgrname;
- const char *membername;
- netgroup_table_t tab;
-} innetgr_cookie_t;
+/* These hold netgroup_t nodes in the cache. */
+static avl_tree_t ngc_cache;
+static list_t ngc_graveyard;
+static list_t ngc_warm_queue;
-typedef unsigned int hash_t;
+/*
+ * ngc_lock must be held while modifying ngc_cache, ngc_graveyard, or adjusting
+ * ng_refcnt in any netgroup in ngc_cache or ngc_graveyard.
+ */
+static mutex_t ngc_lock = ERRORCHECKMUTEX;
+static boolean_t ngc_initialized = B_FALSE;
+
+/*
+ * The warmer thread performs asynchronous refreshes of active netgroups that
+ * are approaching their expiration. Activity is defined as having at least one
+ * use in the final 25% of ngc_pos_ttl.
+ *
+ * Don't think the warmer thread is all rainbows and unicorns: it is also the
+ * grim reaper for expired netgroups. It wakes up every ngc_reap_interval
+ * seconds and clears the cache of expired netgroups.
+ */
+static cond_t ngc_warm_cv = DEFAULTCV;
+static thread_t ngc_warmer_tid;
+static boolean_t ngc_warmer_die = B_FALSE;
+static list_t ngc_pos_expire_queue;
+static list_t ngc_neg_expire_queue;
+static uint32_t ngc_reap_interval = 313; /* Arbitrary, but not N * 60 */
+
+/*
+ * The positive cache size is naturally limited by the size of all the netgroups
+ * in the LDAP server. The negative cache is limited only by imagination,
+ * unless we have an explicit limit on the size.
+ */
+static uint32_t ngc_neg_max = 200; /* Arbitrary */
+static volatile uint32_t ngc_neg_count = 0;
+
+/* Initialized in read_nscd_conf() */
+static boolean_t ngc_enable;
+static int ngc_pos_ttl;
+static int ngc_neg_ttl;
+
+static int split_triple(char *, const char **, const char **, const char **);
+static void ngc_dispose_locked(netgroup_t *);
+
+/*
+ * We don't really care about high-precision timers for cache expiration, but we
+ * do need a monotonic clock. ngc_first_tick is initialized by ngc_init() to
+ * the number of seconds returned by gethrtime(). ngc_last_tick is the number
+ * of seconds since ngc_first_tick.
+ *
+ * ngc_first_tick is a global to allow ngc_init() and ngc_time() to cooperate.
+ * ngc_last_tick is a global so it is available during post-mortem analysis.
+ */
+static uint32_t ngc_first_tick = 0;
+static uint32_t ngc_last_tick = 0;
+
+static uint32_t
+ngc_time(void)
+{
+ uint32_t tick;
+
+ tick = NSEC2SEC(gethrtime()) - ngc_first_tick;
+ DTRACE_PROBE1(nss_ldap, ngc__tick, tick);
+
+ /*
+ * For post-mortem analysis only; the winner of a race will update all
+ * 32-bits at once.
+ */
+ ngc_last_tick = tick;
+
+ return (tick);
+}
+
+static netgroup_t *
+ngc_alloc(const char *name)
+{
+ netgroup_t *ng;
+ size_t len = strlen(name) + 1;
+
+ /*
+ * Use one allocation for the structure and the variable length name.
+ * Not using flexible array member because we need to be able to assign
+ * a value to ng_name before avl_find() without doing a heap allocation.
+ */
+ if ((ng = calloc(1, sizeof (*ng) + len)) == NULL) {
+ return (NULL);
+ }
+
+ ng->ng_birth = ngc_time();
+ ng->ng_name = (const char *)ng + sizeof (*ng);
+ (void) strlcpy((char *)ng->ng_name, name, len);
+
+ return (ng);
+}
+
+static void
+ngc_free(netgroup_t *ng)
+{
+ DTRACE_PROBE2(nss_ldap, netgroup__cache__free, ng->ng_name, ng);
+
+ VERIFY0(ng->ng_refcnt);
+ free(ng->ng_triples);
+ (void) __ns_ldap_freeResult(&ng->ng_result);
+ free(ng);
+}
+
+static void
+ngc_hold_locked(netgroup_t *ng)
+{
+ VERIFY(MUTEX_HELD(&ngc_lock));
+ DTRACE_PROBE3(nss_ldap, netgroup__cache__hold, ng->ng_name, ng,
+ ng->ng_refcnt);
+ ng->ng_refcnt++;
+}
+
+static void
+ngc_rele_locked(netgroup_t *ng)
+{
+ uint32_t now = ngc_time();
+
+ VERIFY(MUTEX_HELD(&ngc_lock));
+ VERIFY3S(ng->ng_refcnt, >, 0);
+
+ DTRACE_PROBE3(nss_ldap, netgroup__cache__rele, ng->ng_name, ng,
+ ng->ng_refcnt);
+
+ ng->ng_refcnt--;
+ if (ng->ng_expire <= now || !NGC_INCACHE(ng)) {
+ ngc_dispose_locked(ng);
+ }
+}
+
+static void
+netgroup_rele(netgroup_t *ng)
+{
+ mutex_enter(&ngc_lock);
+
+ ngc_rele_locked(ng);
+
+ mutex_exit(&ngc_lock);
+}
+
+/*
+ * Free or otherwise properly dispose of a netgroup that is evicted from the
+ * cache or may be ready to be evicted from the graveyard. Does not alter the
+ * reference count.
+ */
+static void
+ngc_dispose_locked(netgroup_t *ng)
+{
+ VERIFY(MUTEX_HELD(&ngc_lock));
+
+ DTRACE_PROBE4(nss_ldap, netgroup__cache__dispose, ng->ng_name, ng,
+ ng->ng_refcnt, ng->ng_flags);
+
+ if (NGC_INCACHE(ng)) {
+ avl_remove(&ngc_cache, ng);
+
+ /*
+ * If it is in the cache, it is also in an expire queue.
+ */
+ if (NGC_NEGATIVE(ng)) {
+ list_remove(&ngc_neg_expire_queue, ng);
+ ngc_neg_count--;
+ } else {
+ list_remove(&ngc_pos_expire_queue, ng);
+ }
+
+ if (ng->ng_refcnt == 0) {
+ ngc_free(ng);
+ } else {
+ NGC_CLEAR(ng, NGC_FLAG_INEXPQUEUE);
+ NGC_CLEAR(ng, NGC_FLAG_INCACHE);
+ DTRACE_PROBE2(nss_ldap, netgroup__cache__to__graveyard,
+ ng->ng_name, ng);
+ list_insert_tail(&ngc_graveyard, ng);
+ }
+ return;
+ }
+
+ /*
+ * If it's not in the cache, it's in the graveyard but may still have
+ * references.
+ */
+ if (ng->ng_refcnt == 0) {
+ list_remove(&ngc_graveyard, ng);
+ ngc_free(ng);
+ return;
+ }
+}
+
+static void
+ngc_set_expire_locked(netgroup_t *ng)
+{
+ list_t *expire_queue;
+ int expire_ttl;
+ int refresh_ttl;
+
+ VERIFY(MUTEX_HELD(&ngc_lock));
+
+ if (NGC_NEGATIVE(ng)) {
+ expire_queue = &ngc_neg_expire_queue;
+ ng->ng_expire = ngc_enable ? (ngc_time() + ngc_neg_ttl) : 0;
+ ng->ng_refresh = 0;
+ } else {
+ expire_queue = &ngc_pos_expire_queue;
+ ng->ng_expire = ngc_enable ?
+ (ngc_time() + ngc_pos_ttl) : 0;
+ /* Refresh when 1/4 or less of ngc_pos_ttl remains */
+ ng->ng_refresh = ngc_enable ?
+ (ng->ng_expire - (ngc_pos_ttl / 4)) : 0;
+ }
+
+ if (NGC_INEXPQUEUE(ng)) {
+ if (list_tail(expire_queue) != ng) {
+ list_remove(expire_queue, ng);
+ list_insert_tail(expire_queue, ng);
+ }
+ } else {
+ NGC_SET(ng, NGC_FLAG_INEXPQUEUE);
+ list_insert_tail(expire_queue, ng);
+ }
+}
+
+/*
+ * This parses the netgroup triples in ng->ng_result, storing them in
+ * ng->ng_triples. The process of parsing them overwrites at least some of the
+ * white space, commas, and parentheses in ng_result so they are not usable
+ * after this. ng->ng_result must live as long as ng->ng_triples.
+ */
+static int
+ngc_parse_triples(netgroup_t *ng)
+{
+ ns_ldap_entry_t *entry;
+ char **attr;
+ uint32_t i = 0;
+ uint32_t entries = 0;
+
+ VERIFY0(ng->ng_triplecnt);
+
+ /* First, we need a count of nisNetgroupTriple attributes */
+ for (entry = ng->ng_result->entry; entry != NULL; entry = entry->next) {
+ entries++;
+ for (attr = __ns_ldap_getAttr(entry, _N_TRIPLE);
+ attr != NULL && *attr != NULL; attr++) {
+ i++;
+ }
+ }
+#ifdef DEBUG
+ syslog(LOG_DEBUG, "ngc_parse_triples parsing %u triples from "
+ "%u entries", i, entries);
+#endif
+ if (i == 0) {
+ return (0);
+ }
+
+ /* Allocate the triples */
+ ng->ng_triples = calloc(i, sizeof (*ng->ng_triples));
+ if (ng->ng_triples == NULL) {
+ ng->ng_triplecnt = 0;
+ return (-1);
+ }
+ ng->ng_triplecnt = i;
+ i = 0;
+
+ /*
+ * Parse the triples. Parse errors lead to not all of the allocated
+ * slots being used.
+ */
+ for (entry = ng->ng_result->entry; entry != NULL; entry = entry->next) {
+ for (attr = __ns_ldap_getAttr(entry, _N_TRIPLE);
+ *attr != NULL; attr++) {
+ ngc_triple_t *ngt = &ng->ng_triples[i];
+
+ if (split_triple(*attr, &ngt->ngt_host,
+ &ngt->ngt_user, &ngt->ngt_domain) == 0) {
+ i++;
+ DTRACE_PROBE5(nss_ldap, netgroup__cache__triple,
+ ng->ng_name, ng, ngt->ngt_host,
+ ngt->ngt_user, ngt->ngt_domain);
+ }
+ }
+ }
+ ng->ng_triplecnt = i;
+
+ return (0);
+}
+
+/*
+ * Get a base10 number that is exactly `digits` long from the string at `*bufp`.
+ * Verify it is between `min` and `max`, inclusive.
+ *
+ * On success `*valp` is updated with the value and `*bufp` is advanced by
+ * `digits` characters.
+ */
+static int
+parse_num(const char **bufp, int *valp, uint32_t digits, int min, int max)
+{
+ uint32_t i;
+ int val = 0;
+ const char *buf = *bufp;
+
+ VERIFY3U(digits, >, 0);
+ for (i = 0; i < digits; i++, buf++) {
+ int newval;
+
+ /*
+ * We avoid isdigit() and isdigit_l() because we are constrained
+ * by RFC 4517 to the ASCII digits, we don't have control over
+ * which locale is currently being used, and newlocale() could
+ * fail.
+ */
+ if (*buf < '0' || *buf > '9') {
+ return (-1);
+ }
+ newval = val * 10 + *buf - '0';
+ if (newval < val) {
+ return (-1);
+ }
+ val = newval;
+ }
+ if (val < min || val > max) {
+ return (-1);
+ }
+ *valp = val;
+ *bufp = buf;
+ return (0);
+}
+
+#ifdef DEBUG
+#define FAILOFF ((uintptr_t)next - (uintptr_t)gtime + 1)
+#define DBG_PARSE_GENTIME_FAIL() \
+ (void) fprintf(stderr, "%s:%d: parse failed at character %lu\n", \
+ __func__, __LINE__, FAILOFF); \
+ (void) fprintf(stderr, " %s\n", gtime); \
+ (void) fprintf(stderr, " %*s\n", FAILOFF, "^");
+#else
+#define DBG_PARSE_GENTIME_FAIL()
+#endif
+
+/*
+ * See RFC 4517 Section 3.3.13
+ *
+ * Tries to find an RFC 4517 compliant Generalized Time in gtime, returning via
+ * *whenp the tenths of seconds since the epoch UTC.
+ *
+ * Times before the epoch are not supported.
+ *
+ * At first blush, it would seem that strptime(3C) should be able to handle this
+ * task. Sadly, that is not the case. In particular, it does not handle
+ * fractional seconds, does not document the support it has for offsets (%z),
+ * and does not support two digit offsets. The one-digit fractional part
+ * specified by RFC 4517 is unlikely to be useful outside of this use case,
+ * so fixing strptime() to be useful here is not reasonable. Even absent the
+ * fractional units issue, the optional components would force many trips
+ * through strptime() trying to guess which format may be the right one.
+ */
+static int
+parse_generalized_time(const char *gtime, uint64_t *whenp)
+{
+ const char *next;
+ struct tm tm = { 0 };
+ uint32_t frac_tenths = 0;
+ int frac = 0;
+ time_t secs;
+ uint64_t tsecs; /* tenths of a second */
+
+ for (next = gtime; *next != '\0'; next++) {
+ if (!isascii(*next)) {
+ DBG_PARSE_GENTIME_FAIL();
+ return (-1);
+ }
+ }
+ next = gtime;
+
+ /* Year, month, day, hour are required */
+ if (parse_num(&next, &tm.tm_year, 4, 0, 9999) != 0 ||
+ parse_num(&next, &tm.tm_mon, 2, 1, 12) != 0 ||
+ parse_num(&next, &tm.tm_mday, 2, 1, 31) != 0 ||
+ parse_num(&next, &tm.tm_hour, 2, 0, 23) != 0) {
+ DBG_PARSE_GENTIME_FAIL();
+ return (-1);
+ }
+
+ /*
+ * Minutes and seconds are optional. The meaning of the fractional part
+ * varies with its position: it may be a fraction of an hour, a minute,
+ * or a second.
+ */
+ frac_tenths = 60 * 60;
+ if (parse_num(&next, &tm.tm_min, 2, 0, 59) == 0) {
+ frac_tenths = 60;
+ if (parse_num(&next, &tm.tm_sec, 2, 0, 60) == 0) {
+ frac_tenths = 1;
+ }
+ }
+
+ /*
+ * (time_t) -1 is an error per timegm(3C); other negative times are
+ * unsupported because we don't realistically expect this to be used
+ * before 1970. If time_t is signed 32-bits, it shouldn't pretend to
+ * parse time properly when the offset from the epoch no longer fits in
+ * 31 bits.
+ */
+ tm.tm_year -= 1900;
+ tm.tm_mon--;
+ if ((secs = timegm(&tm)) < 0) {
+#ifdef DEBUG
+ (void) fprintf(stderr, "%s:%d: timegm failed for <%s>\n",
+ __func__, __LINE__, gtime);
+#endif
+ return (-1);
+ }
+ tsecs = 10 * (uint64_t)secs;
+
+ if (*next == '.' || *next == ',') {
+ next++;
+ if (parse_num(&next, &frac, 1, 0, 9) != 0) {
+ DBG_PARSE_GENTIME_FAIL();
+ return (-1);
+ }
+ tsecs += frac_tenths * frac;
+ }
+
+ if (*next == '+' || *next == '-') {
+ int hrs, mins = 0;
+ int sign = *next == '+' ? -1 : 1;
+
+ next++;
+ /* Get offset hours */
+ if (parse_num(&next, &hrs, 2, 0, 23) != 0) {
+ DBG_PARSE_GENTIME_FAIL();
+ return (-1);
+ }
+ /*
+ * Get offset minutes, which are optional in GeneralizedTime.
+ *
+ * There are three scenarios that may happen now:
+ *
+ * - parse_num() may find two digits that form a number in
+ * [0, 59]. In that case, it will update mins, return
+ * success, and advance the next pointer.
+ *
+ * - parse_num() may find that the next character is a nul
+ * character. This error is rightly ignored because minutes
+ * are optional. The next pointer still references a nul
+ * character.
+ *
+ * - parse_num() may find an invalid value in the next two
+ * characters. In this case, it returns an error without
+ * advancing the next pointer. The nul character check that
+ * follows will detect this situation, causing this function
+ * to return an error.
+ */
+ (void) parse_num(&next, &mins, 2, 0, 59);
+ if (*next != '\0') {
+ DBG_PARSE_GENTIME_FAIL();
+ return (-1);
+ }
+ tsecs += 10 * sign * ((hrs * 60) + mins) * 60;
+ } else if (*next != '\0' && strcmp(next, "Z") != 0) {
+ DBG_PARSE_GENTIME_FAIL();
+ return (-1);
+ }
+
+ *whenp = tsecs;
+
+ return (0);
+}
+
+/*
+ * Get the value of modifyTimestamp and return any valid value in the supplied
+ * buffer.
+ *
+ * RFC 4512 section 3.4.4 defines modifyTimestamp as a GeneralizedTime. RFC
+ * 4517 section 3.3.13 specifies GeneralizedTime as an ISO 8601 time that
+ * may or may not have a fractional component and may or may not have timezone
+ * information. The fractional component can be fractions of seconds, minutes,
+ * or hours, depending on context.
+ */
+static uint64_t
+get_modify_timestamp(ns_ldap_result_t *result)
+{
+ ns_ldap_entry_t *entry;
+ char **attr;
+ uint64_t when;
+
+ for (entry = result->entry; entry != NULL; entry = entry->next) {
+ attr = __ns_ldap_getAttr(entry, _N_MODIFYSTAMP);
+ if (*attr == NULL) {
+ continue;
+ }
+ if (parse_generalized_time(*attr, &when) == 0) {
+ return (when);
+ }
+ }
+ return (0);
+}
+
+static nss_status_t
+ngc_ldap_search(const char *ngname, const char **attrs,
+ ns_ldap_result_t **result)
+{
+ char filter[SEARCHFILTERLEN];
+ char name[SEARCHFILTERLEN];
+ char userdata[SEARCHFILTERLEN];
+ ns_ldap_error_t *error = NULL;
+ int rc;
+
+ /* Escape special characters */
+ if (_ldap_filter_name(name, ngname, sizeof (name)) != 0)
+ return (NSS_NOTFOUND);
+ /* Form "(&(objectClass=nisNetGroup)(cn=<name>))" */
+ rc = snprintf(filter, sizeof (filter), _F_SETMEMBER, name);
+ if (rc >= sizeof (filter) || rc < 0)
+ return (NSS_NOTFOUND);
+
+ /* Form "(&(%s)(cn=<name>))" - including literal %s */
+ rc = snprintf(userdata, sizeof (userdata), _F_SETMEMBER_SSD, name);
+ if (rc >= sizeof (userdata) || rc < 0) {
+ return (NSS_NOTFOUND);
+ }
+
+ /* Perform the search */
+ rc = __ns_ldap_list(_NETGROUP, filter, _merge_SSD_filter, attrs, NULL,
+ 0, result, &error, NULL, userdata);
+ if (error != NULL && switch_err(rc, error) == NSS_TRYAGAIN) {
+ /*
+ * Return NSS_TRYAGAIN (rather than looping here) so that the
+ * nscd or the name service switch frontend can manage the
+ * retries.
+ */
+ (void) __ns_ldap_freeError(&error);
+ return (NSS_TRYAGAIN);
+ }
+ (void) __ns_ldap_freeError(&error);
+ if (rc != NS_LDAP_SUCCESS) {
+ return (NSS_NOTFOUND);
+ }
+
+ return (NSS_SUCCESS);
+}
+
+/*
+ * Get the specified netgroup from LDAP. Only to be called by netgroup_get() or
+ * ng_refresh();
+ *
+ * On successful read from ldap, the netgroup is added to the cache and a held
+ * reference is returned. If the cache is enabled, a lookup that returns
+ * NSS_NOTFOUND will lead to a negative cache entry.
+ */
+static nss_status_t
+ngc_get_from_ldap(const char *ngname, netgroup_t **ngp)
+{
+ char filter[SEARCHFILTERLEN];
+ char name[SEARCHFILTERLEN];
+ char userdata[SEARCHFILTERLEN];
+ ns_ldap_result_t *result = NULL;
+ ns_ldap_error_t *error = NULL;
+ int rc;
+ nss_status_t status;
+ netgroup_t *ng, *ngc;
+
+ VERIFY(!MUTEX_HELD(&ngc_lock));
+
+ status = ngc_ldap_search(ngname, netgrent_attrs, &result);
+ if (status == NSS_NOTFOUND && ngc_enable && ngc_neg_ttl > 0) {
+ /* Add a negative entry, being careful not to allow too many */
+ mutex_enter(&ngc_lock);
+ if (ngc_neg_count >= ngc_neg_max) {
+ mutex_exit(&ngc_lock);
+ return (NSS_NOTFOUND);
+ }
+ ngc_neg_count++;
+ mutex_exit(&ngc_lock);
+
+ if ((ng = ngc_alloc(ngname)) == NULL) {
+ return (status);
+ }
+ NGC_SET(ng, NGC_FLAG_NEGATIVE | NGC_FLAG_INCACHE);
+ } else if (status != NSS_SUCCESS) {
+ return (status);
+ } else {
+ /* We got a result, cache it. */
+ if ((ng = ngc_alloc(ngname)) == NULL) {
+ int err = errno;
+ (void) __ns_ldap_freeResult(&result);
+ errno = err;
+ return (NSS_ERROR);
+ }
+
+ ng->ng_result = result;
+ if (ngc_parse_triples(ng) != 0) {
+ return (NSS_ERROR);
+ }
+
+ ng->ng_lastchange = get_modify_timestamp(result);
+
+ NGC_SET(ng, NGC_FLAG_INCACHE);
+ *ngp = ng;
+ }
+
+ mutex_enter(&ngc_lock);
+
+ if ((ngc = avl_find(&ngc_cache, ng, NULL)) != NULL) {
+ /* Someone else just slipped one in. This one is newer? */
+ DTRACE_PROBE3(nss_ldap, netgroup__cache__add__collision,
+ ng->ng_name, ng, ngc);
+ ngc_rele_locked(ngc);
+ }
+
+ ngc_set_expire_locked(ng);
+ avl_add(&ngc_cache, ng);
+
+ status = NGC_NEGATIVE(ng) ? NSS_NOTFOUND : NSS_SUCCESS;
+ if (status == NSS_SUCCESS) {
+ ngc_hold_locked(ng);
+ *ngp = ng;
+ }
+
+ mutex_exit(&ngc_lock);
+
+ DTRACE_PROBE3(nss_ldap, netgroup__cache__add, ng->ng_name, ng, status);
+ return (status);
+}
+
+static void
+ngc_queue_refresh_locked(netgroup_t *ng)
+{
+ VERIFY(MUTEX_HELD(&ngc_lock));
+
+ DTRACE_PROBE2(nss_ldap, netgroup__warmer__enqueue, ng->ng_name, ng);
+
+ NGC_SET(ng, NGC_FLAG_INWARMER);
+ list_insert_tail(&ngc_warm_queue, ng);
+
+ VERIFY0(cond_signal(&ngc_warm_cv));
+}
+
+/*
+ * Get the specified netgroup from the cache.
+ */
+static nss_status_t
+netgroup_get(const char *name, netgroup_t **ngp)
+{
+ netgroup_t *ng;
+ netgroup_t find = { .ng_name = name };
+ uint32_t now;
+ nss_status_t status;
+
+ mutex_enter(&ngc_lock);
+
+ if (!ngc_initialized) {
+ /*
+ * A poorly behaved application may be trying lookups while
+ * simultaneously calling dlclose().
+ */
+ mutex_exit(&ngc_lock);
+ errno = ENOSYS;
+ return (NSS_ERROR);
+ }
+
+ ng = avl_find(&ngc_cache, &find, NULL);
+ if (ng == NULL) {
+ /* not in cache, get it from LDAP */
+ mutex_exit(&ngc_lock);
+ return (ngc_get_from_ldap(name, ngp));
+ }
+ VERIFY(NGC_INCACHE(ng));
+
+ /*
+ * If the netgroup has expired, get it out of the cache and get it fresh
+ * from LDAP.
+ */
+ now = ngc_time();
+ if (ng->ng_expire <= now) {
+ ngc_dispose_locked(ng);
+ mutex_exit(&ngc_lock);
+ return (ngc_get_from_ldap(name, ngp));
+ }
+
+ /*
+ * If a refresh is needed, grab one ref for the return and another for
+ * the refresh. Set the refresh time forward so that we don't end up
+ * with concurrent refreshes.
+ */
+ if (ngc_enable && ng->ng_refresh < now && !NGC_INWARMER(ng) &&
+ !NGC_NEGATIVE(ng)) {
+ ng->ng_refresh = ng->ng_expire;
+ ngc_hold_locked(ng);
+ ngc_queue_refresh_locked(ng);
+ }
+ status = NGC_NEGATIVE(ng) ? NSS_NOTFOUND : NSS_SUCCESS;
+ if (status == NSS_SUCCESS) {
+ ngc_hold_locked(ng);
+ *ngp = ng;
+ }
+ mutex_exit(&ngc_lock);
+
+ DTRACE_PROBE3(nss_ldap, netgroup__get__from__cache, ng->ng_name, ng,
+ status);
+ return (status);
+}
+
+/*
+ * Dispose of all expired netgroups that are in the cache.
+ */
+static void
+ngc_reap_locked(void)
+{
+ list_t *queues[] = {
+ &ngc_neg_expire_queue,
+ &ngc_pos_expire_queue,
+ };
+ netgroup_t *ng;
+ uint32_t now = ngc_time();
+ uint32_t i;
+
+ VERIFY(MUTEX_HELD(&ngc_lock));
+
+ for (i = 0; i < ARRAY_SIZE(queues); i++) {
+ for (ng = list_head(queues[i]);
+ ng != NULL && now >= ng->ng_expire;
+ ng = list_head(queues[i])) {
+ DTRACE_PROBE3(nss_ldap, netgroup__reap, ng->ng_name, ng,
+ queues[i]);
+ VERIFY(NGC_INCACHE(ng));
+ ngc_dispose_locked(ng);
+ VERIFY3P(list_head(queues[i]), !=, ng);
+ }
+ }
+}
+
+/*
+ * This worker thread picks up netgroups that need to be refreshed from
+ * ngc_warm_queue. It also wakes up from time to time (ngc_reap_interval) to
+ * clear cruft from the cache.
+ *
+ * Several things can happen to a netgroup that is in this queue.
+ *
+ * - There could be a delay in queue processing and the netgroup may have
+ * already been evicted from the cache. In this case it is not refreshed.
+ * - The LDAP server may not provide a modifyTimestamp attr. In this case, the
+ * netgroup will be fully reloaded on demand.
+ * - Most commonly, the current modifyTimestamp value matches the value found in
+ * the cache. The expire and refresh times are updated as though the entire
+ * netgroup was just loaded.
+ * - A newer modifyTimestamp may be seen. This causes the netgroup to be
+ * expired from the cache.
+ *
+ * As each netgroup was placed in ngc_warm_queue, a reference was taken. That
+ * reference is released as this thread processes the renewal.
+ */
+static void *
+ngc_warmer(void *data __unused)
+{
+ netgroup_t *ng;
+ netgroup_t *newng;
+ nss_status_t status;
+ ns_ldap_result_t *result = NULL;
+ avl_index_t where;
+ uint64_t lastchange;
+ timestruc_t reltime;
+ int err;
+
+ mutex_enter(&ngc_lock);
+
+ reltime.tv_sec = ngc_reap_interval;
+ reltime.tv_nsec = 0;
+
+ for (;;) {
+ err = cond_reltimedwait(&ngc_warm_cv, &ngc_lock, &reltime);
+ VERIFY(err == 0 || err == ETIME || err == EINTR);
+
+ if (ngc_warmer_die) {
+ mutex_exit(&ngc_lock);
+ break;
+ }
+
+ /*
+ * First, do a little housekeeping.
+ */
+ ngc_reap_locked();
+
+ ng = list_remove_head(&ngc_warm_queue);
+ if (ng != NULL) {
+ NGC_CLEAR(ng, NGC_FLAG_INWARMER);
+ }
+
+ if (ng == NULL) {
+ /* Timeout or interrupted by a signal */
+ continue;
+ }
+
+ mutex_exit(&ngc_lock);
+ status = ngc_ldap_search(ng->ng_name, netgrent_stamp, &result);
+ mutex_enter(&ngc_lock);
+ if (status != NSS_SUCCESS) {
+ /*
+ * Either the server does not support modifyTimestamp or
+ * something worse happened. Since we aren't certain of
+ * the reason, do not evict it from the cache.
+ */
+ DTRACE_PROBE3(nss_ldap, netgroup__warmer__ldap__fail,
+ ng->ng_name, ng, status);
+ ngc_rele_locked(ng);
+ continue;
+ }
+ lastchange = get_modify_timestamp(result);
+ (void) __ns_ldap_freeResult(&result);
+ if (lastchange == 0) {
+ DTRACE_PROBE2(nss_ldap, netgroup__warmer__no__stamp,
+ ng->ng_name, ng);
+ ngc_rele_locked(ng);
+ continue;
+ }
+
+ if (lastchange == ng->ng_lastchange && !NGC_INCACHE(ng) &&
+ avl_find(&ngc_cache, ng, &where) == NULL) {
+ /*
+ * The netgroup has not changed, but it has been
+ * expired. Rip it from the jaws of death.
+ */
+
+ DTRACE_PROBE2(nss_ldap, netgroup__warmer__resurrection,
+ ng->ng_name, ng);
+ list_remove(&ngc_graveyard, ng);
+ avl_insert(&ngc_cache, ng, where);
+ NGC_SET(ng, NGC_FLAG_INCACHE);
+ ngc_set_expire_locked(ng);
+
+ ngc_rele_locked(ng);
+ continue;
+ }
+ if (lastchange == ng->ng_lastchange) {
+ /* Netgroup has not changed, move expiry ahead */
+
+ DTRACE_PROBE2(nss_ldap, netgroup__warmer__renewal,
+ ng->ng_name, ng);
+ ngc_set_expire_locked(ng);
+ ngc_rele_locked(ng);
+ continue;
+ }
+
+ /*
+ * The netgroup has been changed. Expire the current netgroup
+ * then fetch a fresh copy. That order is important so that the
+ * netgroup is not in the cache when ngc_get_from_ldap() tries
+ * to add the fresh copy. Keep the hold on ng until after
+ * ngc_get_from_ldap() completes to ensure that ng->ng_name does
+ * not get freed while it is still needed.
+ */
+ DTRACE_PROBE2(nss_ldap, netgroup__warmer__expire, ng->ng_name,
+ ng);
+ ngc_dispose_locked(ng);
+
+ mutex_exit(&ngc_lock);
+ status = ngc_get_from_ldap(ng->ng_name, &newng);
+ mutex_enter(&ngc_lock);
+
+ if (status == NSS_SUCCESS) {
+ DTRACE_PROBE3(nss_ldap,
+ netgroup__warmer__reload__success, newng->ng_name,
+ newng, ng);
+ ngc_rele_locked(newng);
+ } else {
+ DTRACE_PROBE3(nss_ldap, netgroup__warmer__reload__fail,
+ ng->ng_name, ng, status);
+ }
+ ngc_rele_locked(ng);
+ }
+
+ return (NULL);
+}
+
+/*
+ * Netgroup table management. This is used during a query to handle nested
+ * netgroups while avoiding loops.
+ */
static hash_t
get_hash(const char *s)
@@ -121,12 +1245,12 @@ add_netgroup_name(const char *name, netgroup_table_t *tab)
}
h = get_hash(name);
- ng = tab->hash_list[h];
+ ng = tab->ngt_hash_list[h];
while (ng != NULL) {
- if (strcmp(name, ng->name) == 0)
+ if (strcmp(name, ng->ngn_name) == 0)
break;
- ng = ng->next_hash;
+ ng = ng->ngn_next_hash;
}
if (ng == NULL) {
@@ -134,15 +1258,15 @@ add_netgroup_name(const char *name, netgroup_table_t *tab)
calloc(1, sizeof (netgroup_name_t));
if (ng_new == NULL)
return (-1);
- ng_new->name = strdup(name);
- if (ng_new->name == NULL) {
+ ng_new->ngn_name = strdup(name);
+ if (ng_new->ngn_name == NULL) {
free(ng_new);
return (-1);
}
- ng_new->next_hash = tab->hash_list[h];
- tab->hash_list[h] = ng_new;
- ng_new->next = tab->to_do;
- tab->to_do = ng_new;
+ ng_new->ngn_next_hash = tab->ngt_hash_list[h];
+ tab->ngt_hash_list[h] = ng_new;
+ ng_new->ngn_next = tab->ngt_to_do;
+ tab->ngt_to_do = ng_new;
}
return (0);
}
@@ -155,11 +1279,11 @@ get_next_netgroup(netgroup_table_t *tab)
if (tab == NULL)
return (NULL);
- ng = tab->to_do;
+ ng = tab->ngt_to_do;
if (ng != NULL) {
- tab->to_do = ng->next;
- ng->next = tab->done;
- tab->done = ng;
+ tab->ngt_to_do = ng->ngn_next;
+ ng->ngn_next = tab->ngt_done;
+ tab->ngt_done = ng;
}
return (ng);
}
@@ -172,17 +1296,17 @@ free_netgroup_table(netgroup_table_t *tab)
if (tab == NULL)
return;
- for (ng = tab->to_do; ng != NULL; ng = next) {
- if (ng->name != NULL)
- free(ng->name);
- next = ng->next;
+ for (ng = tab->ngt_to_do; ng != NULL; ng = next) {
+ if (ng->ngn_name != NULL)
+ free(ng->ngn_name);
+ next = ng->ngn_next;
free(ng);
}
- for (ng = tab->done; ng != NULL; ng = next) {
- if (ng->name != NULL)
- free(ng->name);
- next = ng->next;
+ for (ng = tab->ngt_done; ng != NULL; ng = next) {
+ if (ng->ngn_name != NULL)
+ free(ng->ngn_name);
+ next = ng->ngn_next;
free(ng);
}
(void) memset(tab, 0, sizeof (*tab));
@@ -190,8 +1314,8 @@ free_netgroup_table(netgroup_table_t *tab)
/*
* domain comparing routine
- * n1: See if n1 is n2 or an ancestor of it
- * n2: (in string terms, n1 is a suffix of n2)
+ * n1: See if n1 is n2 or an ancestor of it
+ * n2: (in string terms, n1 is a suffix of n2)
* Returns ZERO for success, -1 for failure.
*/
static int
@@ -233,14 +1357,15 @@ domcmp(const char *n1, const char *n2)
}
static int
-split_triple(char *triple, char **hostname, char **username, char **domain)
+split_triple(char *triple, const char **hostname, const char **username,
+ const char **domain)
{
int i, syntax_err;
char *splittriple[3];
char *p = triple;
#ifdef DEBUG
- (void) fprintf(stdout, "\n[getnetgrent.c: split_triple]\n");
+ (void) fprintf(stderr, "\n[getnetgrent.c: split_triple]\n");
#endif /* DEBUG */
if (triple == NULL)
@@ -308,7 +1433,7 @@ split_triple(char *triple, char **hostname, char **username, char **domain)
*/
static int
-match_triple_entry(struct nss_innetgr_args *ia, const ns_ldap_entry_t *entry)
+match_triple(struct nss_innetgr_args *ia, netgroup_t *ng)
{
int ndomains;
char **pdomains;
@@ -316,13 +1441,9 @@ match_triple_entry(struct nss_innetgr_args *ia, const ns_ldap_entry_t *entry)
char **phost;
int nusers;
char **pusers;
- char **attr;
- char triple[MAX_TRIPLE_LEN];
- char *tuser, *thost, *tdomain;
- int i;
- char *current, *limit;
- int pulen, phlen;
- char *pusers0, *phost0;
+ const char *tuser, *thost, *tdomain;
+ uint32_t i, trip;
+ char *pusers0 = NULL, *phost0 = NULL;
nhost = ia->arg[NSS_NETGR_MACHINE].argc;
phost = (char **)ia->arg[NSS_NETGR_MACHINE].argv;
@@ -330,7 +1451,6 @@ match_triple_entry(struct nss_innetgr_args *ia, const ns_ldap_entry_t *entry)
nhost = 0;
} else {
phost0 = phost[0];
- phlen = strlen(phost0);
#ifdef DEBUG
syslog(LOG_DEBUG, "nss_ldap: match_triple_entry: "
"entering with host: %s", phost0 ? phost0 : "");
@@ -342,7 +1462,6 @@ match_triple_entry(struct nss_innetgr_args *ia, const ns_ldap_entry_t *entry)
nusers = 0;
} else {
pusers0 = pusers[0];
- pulen = strlen(pusers0);
#ifdef DEBUG
syslog(LOG_DEBUG, "nss_ldap: match_triple_entry: "
"entering with user: %s", pusers0 ? pusers0 : "");
@@ -358,10 +1477,6 @@ match_triple_entry(struct nss_innetgr_args *ia, const ns_ldap_entry_t *entry)
"entering with domain: %s", pdomains[0] ? pdomains[0] : "");
#endif
- attr = __ns_ldap_getAttr(entry, _N_TRIPLE);
- if (attr == NULL || *attr == NULL)
- return (0);
-
#ifdef DEBUG
syslog(LOG_DEBUG, "nss_ldap: match_triple_entry: "
"(nusers: %d, nhost:%d, ndomains: %d)",
@@ -369,104 +1484,56 @@ match_triple_entry(struct nss_innetgr_args *ia, const ns_ldap_entry_t *entry)
#endif
/* Special cases for speedup */
- if (nusers == 1 && nhost == 0 && ndomains == 0) {
+ if (nusers == 1 && nhost == 0 && ndomains == 0 && pusers0 != NULL) {
/* Special case for finding a single user in a netgroup */
- for (; *attr; attr++) {
- /* jump to first comma and check next character */
- current = *attr;
+ for (trip = 0; trip < ng->ng_triplecnt; trip++) {
+ ngc_triple_t *ngt = &ng->ng_triples[trip];
+
#ifdef DEBUG
syslog(LOG_DEBUG, "nss_ldap: match_triple_entry: "
- "current is: %s", current);
+ "current user is: %s", ngt->ngt_user);
#endif
- if ((current = strchr(current, COMMA)) == NULL)
- continue;
- current++;
-
- /* skip whitespaces */
- while (isspace(*current))
- current++;
-
/* if user part is null, then treat as wildcard */
- if (*current == COMMA)
+ if (ngt->ngt_user == NULL) {
return (1);
-
- /* compare first character */
- if (*pusers0 != *current)
- continue;
-
- /* limit username to COMMA */
- if ((limit = strchr(current, COMMA)) == NULL)
- continue;
- *limit = '\0';
-
- /* remove blanks before COMMA */
- if ((limit = strpbrk(current, " \t")) != NULL)
- *limit = '\0';
-
- /* compare size of username */
- if (pulen != strlen(current)) {
- continue;
}
/* do actual compare */
- if (strncmp(pusers0, current, pulen) == 0) {
+ if (strcmp(pusers0, ngt->ngt_user) == 0) {
return (1);
- } else {
- continue;
}
}
- } else if (nusers == 0 && nhost == 1 && ndomains == 0) {
+ } else if (nusers == 0 && nhost == 1 && ndomains == 0 &&
+ phost0 != NULL) {
/* Special case for finding a single host in a netgroup */
- for (; *attr; attr++) {
+ for (trip = 0; trip < ng->ng_triplecnt; trip++) {
+ ngc_triple_t *ngt = &ng->ng_triples[trip];
+
- /* jump to first character and check */
- current = *attr;
#ifdef DEBUG
syslog(LOG_DEBUG, "nss_ldap: match_triple_entry: "
- "current is: %s", current);
+ "current host is: %s", ngt->ngt_host);
#endif
- current++;
-
- /* skip whitespaces */
- while (isspace(*current))
- current++;
/* if host part is null, then treat as wildcard */
- if (*current == COMMA)
+ if (ngt->ngt_host == NULL) {
return (1);
-
- /* limit hostname to COMMA */
- if ((limit = strchr(current, COMMA)) == NULL)
- continue;
- *limit = '\0';
-
- /* remove blanks before COMMA */
- if ((limit = strpbrk(current, " \t")) != NULL)
- *limit = '\0';
-
- /* compare size of hostname */
- if (phlen != strlen(current)) {
- continue;
}
/* do actual compare */
- if (strncasecmp(phost0, current, phlen) == 0) {
+ if (strcasecmp(phost0, ngt->ngt_host) == 0) {
return (1);
- } else {
- continue;
}
}
} else {
- for (; *attr; attr++) {
- if (strlcpy(triple, *attr,
- sizeof (triple)) >= sizeof (triple))
- continue;
+ for (trip = 0; trip < ng->ng_triplecnt; trip++) {
+ thost = ng->ng_triples[trip].ngt_host;
+ tuser = ng->ng_triples[trip].ngt_user;
+ tdomain = ng->ng_triples[trip].ngt_domain;
#ifdef DEBUG
syslog(LOG_DEBUG, "nss_ldap: match_triple_entry: "
- "triple is: %s", triple);
+ "triple is: (%s,%s,%s)", thost, tuser, tdomain);
#endif
- if (split_triple(triple, &thost, &tuser, &tdomain) != 0)
- continue;
if (thost != NULL && *thost != '\0' && nhost != 0) {
for (i = 0; i < nhost; i++)
if (strcasecmp(thost, phost[i]) == 0)
@@ -497,18 +1564,6 @@ match_triple_entry(struct nss_innetgr_args *ia, const ns_ldap_entry_t *entry)
}
static int
-match_triple(struct nss_innetgr_args *ia, ns_ldap_result_t *result)
-{
- ns_ldap_entry_t *entry;
-
- for (entry = result->entry; entry != NULL; entry = entry->next)
- if (match_triple_entry(ia, entry) == 1)
- return (1);
-
- return (0);
-}
-
-static int
add_netgroup_member_entry(ns_ldap_entry_t *entry, netgroup_table_t *tab)
{
char **attrs;
@@ -548,83 +1603,59 @@ add_netgroup_member(ns_ldap_result_t *result, netgroup_table_t *tab)
static nss_status_t
top_down_search(struct nss_innetgr_args *ia, char *netgrname)
{
- char searchfilter[SEARCHFILTERLEN];
- char name[SEARCHFILTERLEN];
- char userdata[SEARCHFILTERLEN];
- ns_ldap_result_t *result = NULL;
- ns_ldap_error_t *error = NULL;
- int rc;
- nss_status_t status = NSS_NOTFOUND;
- nss_status_t status1;
- netgroup_table_t tab;
- netgroup_name_t *ng;
- int ret;
+ netgroup_table_t tab;
+ netgroup_name_t *ngn;
+ netgroup_t *ng;
+ int rc;
+ int serrno;
(void) memset(&tab, 0, sizeof (tab));
if (add_netgroup_name(netgrname, &tab) != 0)
return ((nss_status_t)NSS_NOTFOUND);
- while ((ng = get_next_netgroup(&tab)) != NULL) {
+ while ((ngn = get_next_netgroup(&tab)) != NULL) {
#ifdef DEBUG
syslog(LOG_DEBUG, "nss_ldap: top_down_search: netgroup loop "
- "(ng->name: %s)", ng->name ? ng->name : "null !");
+ "(ngn->ngn_name: %s)",
+ ngn->ngn_name ? ngn->ngn_name : "null !");
#endif
- if (_ldap_filter_name(name, ng->name, sizeof (name)) != 0)
- break;
- ret = snprintf(searchfilter, sizeof (searchfilter),
- _F_SETMEMBER, name);
- if (ret >= sizeof (searchfilter) || ret < 0)
+ switch (netgroup_get(ngn->ngn_name, &ng)) {
+ case NSS_SUCCESS:
break;
-
- ret = snprintf(userdata, sizeof (userdata), _F_SETMEMBER_SSD,
- name);
- if (ret >= sizeof (userdata) || ret < 0)
- break;
-
- /* searching for current netgroup name entry */
- rc = __ns_ldap_list(_NETGROUP, searchfilter,
- _merge_SSD_filter, netgrent_attrs, NULL, 0, &result,
- &error, NULL, userdata);
-
- if (error != NULL) {
- status1 = switch_err(rc, error);
- if (status1 == NSS_TRYAGAIN) {
- (void) __ns_ldap_freeError(&error);
- free_netgroup_table(&tab);
- return (status1);
- }
+ case NSS_TRYAGAIN:
+ free_netgroup_table(&tab);
+ return (NSS_TRYAGAIN);
+ case NSS_ERROR:
+ serrno = errno;
+ free_netgroup_table(&tab);
+ errno = serrno;
+ return (NSS_ERROR);
+ default:
+ continue;
}
- (void) __ns_ldap_freeError(&error);
- if (rc == NS_LDAP_SUCCESS) {
- if (match_triple(ia, result) == 1) {
- /* We found a match */
- ia->status = NSS_NETGR_FOUND;
- status = NSS_SUCCESS;
+ if (match_triple(ia, ng) == 1) {
+ /* We found a match */
+ ia->status = NSS_NETGR_FOUND;
+ free_netgroup_table(&tab);
+ netgroup_rele(ng);
#ifdef DEBUG
- syslog(LOG_DEBUG, "nss_ldap: top_down_search: "
- "found match");
+ syslog(LOG_DEBUG, "nss_ldap: top_down_search: "
+ "found match\n");
#endif
- break;
- }
+ return (NSS_SUCCESS);
+ }
- /*
- * No match found. Check for membernisnetgroup
- * in result and if yes, start again with those.
- */
- rc = add_netgroup_member(result, &tab);
- if (rc != 0)
- break;
- } else if (rc != NS_LDAP_NOTFOUND) {
+ rc = add_netgroup_member(ng->ng_result, &tab);
+ netgroup_rele(ng);
+ if (rc != 0) {
break;
}
- (void) __ns_ldap_freeResult(&result);
}
- (void) __ns_ldap_freeResult(&result);
free_netgroup_table(&tab);
- return (status);
+ return (NSS_NOTFOUND);
}
/*
@@ -637,8 +1668,8 @@ __netgr_in(void *a, char *netgrname)
nss_status_t status = NSS_NOTFOUND;
#ifdef DEBUG
- (void) fprintf(stdout, "\n[getnetgrent.c: netgr_in]\n");
- (void) fprintf(stdout, "\tmachine: argc[%d]='%s' user: "
+ (void) fprintf(stderr, "\n[getnetgrent.c: netgr_in]\n");
+ (void) fprintf(stderr, "\tmachine: argc[%d]='%s' user: "
"argc[%d]='%s',\n\tdomain:argc[%d]='%s' "
"netgroup: argc[%d]='%s'\n",
NSS_NETGR_MACHINE,
@@ -649,7 +1680,7 @@ __netgr_in(void *a, char *netgrname)
PRINT_VAL(ia->arg[NSS_NETGR_DOMAIN]),
NSS_NETGR_N,
PRINT_VAL(ia->arg[NSS_NETGR_N]));
- (void) fprintf(stdout, "\tgroups='%s'\n", netgrname);
+ (void) fprintf(stderr, "\tgroups='%s'\n", netgrname);
#endif /* DEBUG */
ia->status = NSS_NETGR_NO;
@@ -657,7 +1688,12 @@ __netgr_in(void *a, char *netgrname)
if (netgrname == NULL)
return (status);
- return (top_down_search(ia, netgrname));
+ status = top_down_search(ia, netgrname);
+ DTRACE_PROBE5(nss_ldap, innetgr, netgrname,
+ PRINT_VAL(ia->arg[NSS_NETGR_MACHINE]),
+ PRINT_VAL(ia->arg[NSS_NETGR_USER]),
+ PRINT_VAL(ia->arg[NSS_NETGR_DOMAIN]), status);
+ return (status);
}
/*ARGSUSED0*/
@@ -669,6 +1705,7 @@ netgr_in(ldap_backend_ptr be, void *a)
nss_status_t rc = (nss_status_t)NSS_NOTFOUND;
ia->status = NSS_NETGR_NO;
+
for (i = 0; i < ia->groups.argc; i++) {
rc = __netgr_in(a, ia->groups.argv[i]);
if (ia->status == NSS_NETGR_FOUND)
@@ -677,45 +1714,23 @@ netgr_in(ldap_backend_ptr be, void *a)
return (rc);
}
-/*
- *
- */
-
-static nss_status_t
-getnetgr_ldap_setent(ldap_backend_ptr be, void *a)
-{
- const char *netgroup = (const char *) a;
- getnetgrent_cookie_t *cookie;
-
-#ifdef DEBUG
- (void) fprintf(stdout, "\n[getnetgrent.c: getnetgr_ldap_setent]\n");
-#endif /* DEBUG */
-
- cookie = (getnetgrent_cookie_t *)be->netgroup_cookie;
- if (cookie != NULL && cookie->netgroup != NULL) {
- /* is this another set on the same netgroup */
- if (strcmp(cookie->netgroup, netgroup) == 0)
- return ((nss_status_t)NSS_SUCCESS);
- }
-
- return (NSS_NOTFOUND);
-}
-
static void
free_getnetgrent_cookie(getnetgrent_cookie_t **cookie)
{
getnetgrent_cookie_t *p = *cookie;
#ifdef DEBUG
- (void) fprintf(stdout, "\n[getnetgrent.c: free_getnetgrent_cookie]\n");
+ (void) fprintf(stderr, "\n[getnetgrent.c: free_getnetgrent_cookie]\n");
#endif /* DEBUG */
if (p == NULL)
return;
- (void) __ns_ldap_freeResult(&p->results);
- free_netgroup_table(&p->tab);
- free(p->netgroup);
+ if (p->gnc_netgroup != NULL) {
+ netgroup_rele(p->gnc_netgroup);
+ }
+ free_netgroup_table(&p->gnc_tab);
+ free(p->gnc_name);
free(p);
*cookie = NULL;
}
@@ -726,7 +1741,7 @@ getnetgr_ldap_endent(ldap_backend_ptr be, void *a)
{
#ifdef DEBUG
- (void) fprintf(stdout, "\n[getnetgrent.c: getnetgr_ldap_endent]\n");
+ (void) fprintf(stderr, "\n[getnetgrent.c: getnetgr_ldap_endent]\n");
#endif /* DEBUG */
free_getnetgrent_cookie((getnetgrent_cookie_t **)&be->netgroup_cookie);
@@ -741,7 +1756,7 @@ getnetgr_ldap_destr(ldap_backend_ptr be, void *a)
{
#ifdef DEBUG
- (void) fprintf(stdout, "\n[getnetgrent.c: getnetgr_ldap_destr]\n");
+ (void) fprintf(stderr, "\n[getnetgrent.c: getnetgr_ldap_destr]\n");
#endif /* DEBUG */
free_getnetgrent_cookie((getnetgrent_cookie_t **)&be->netgroup_cookie);
@@ -750,27 +1765,81 @@ getnetgr_ldap_destr(ldap_backend_ptr be, void *a)
return ((nss_status_t)NSS_NOTFOUND);
}
+/*
+ * Copies results from a buffer that may be about to be freed into a long-lived
+ * general-purpose buffer.
+ *
+ * val IN: The return value that needs to be copied.
+ *
+ * *bufferp IN: On the first call of this function for a particular nss
+ * call, this should be the address of `buffer` element of a
+ * nss_getnetgrent_args structure (`args`). On subsequent
+ * calls, it should be the value that was returned by
+ * reference from the previous call. Do not pass
+ * `&args->buffer`, rather pass a reference to a copy of
+ * `&args->buffer`.
+ * OUT: Advanced to the next unused space in args->buffer.
+ *
+ * *leftp IN: The amount of space in `args->buffer` that remains unused
+ * and available for copying `val` into `args->buffer`.
+ * The first call should pass a reference to a copy of
+ * `args->buflen` and subsequent calls should use the value
+ * returned by the previous call.
+ * OUT: The amount of space that remains after copying `val`.
+ *
+ * *retbufp OUT: Will be updated to reference the location in args->buffer
+ * that contains a copy of val. Typically will be one of
+ * args->retp[].
+ *
+ * After the following calls (plus error checking of function returns)
+ *
+ * char *buf = args->result;
+ * size_t left = args->buflen;
+ * set_retbuf("host", &buf, &left, &args->retp[NSS_NETGR_MACHINE]);
+ * set_retbuf("user", &buf, &left, &args->retp[NSS_NETGR_USER]);
+ * set_retbuf("domain", &buf, &left, &args->retp[NSS_NETGR_DOMAIN]);
+ *
+ * `args` looks like:
+ *
+ * buffer = "host\0user\0domain\0"
+ * ^ ^ ^
+ * | | retp[NSS_NETGR_DOMAIN]
+ * | retp[NSS_NETGR_USER]
+ * retp[NSS_NETGR_HOST]
+ */
+static int
+set_retbuf(const char *val, char **bufferp, size_t *leftp, char **retbufp)
+{
+ char *buffer = *bufferp;
+ size_t left = *leftp;
+ size_t len;
+
+ if (val == NULL) {
+ *retbufp = NULL;
+ return (0);
+ }
+ len = strlcpy(buffer, val, left);
+ if (len >= left) {
+ return (-1);
+ }
+ *retbufp = buffer;
+ *bufferp = buffer + len;
+ *leftp = left - len;
+ return (0);
+}
static nss_status_t
getnetgr_ldap_getent(ldap_backend_ptr be, void *a)
{
struct nss_getnetgrent_args *args;
getnetgrent_cookie_t *p;
- char searchfilter[SEARCHFILTERLEN];
- char userdata[SEARCHFILTERLEN];
- char name[SEARCHFILTERLEN];
- int rc;
- ns_ldap_result_t *result = NULL;
- ns_ldap_error_t *error = NULL;
- char **attrs;
- char *hostname, *username, *domain;
- char *buffer;
nss_status_t status = NSS_SUCCESS;
- netgroup_name_t *ng;
+ netgroup_name_t *ngn;
int ret;
+ ns_ldap_result_t *results = NULL;
#ifdef DEBUG
- (void) fprintf(stdout, "\n[getnetgrent.c: getnetgr_ldap_getent]\n");
+ (void) fprintf(stderr, "\n[getnetgrent.c: getnetgr_ldap_getent]\n");
#endif /* DEBUG */
args = (struct nss_getnetgrent_args *)a;
@@ -778,8 +1847,9 @@ getnetgr_ldap_getent(ldap_backend_ptr be, void *a)
args->status = NSS_NETGR_NO;
p = (getnetgrent_cookie_t *)be->netgroup_cookie;
- if (p == NULL)
+ if (p == NULL) {
return ((nss_status_t)NSS_SUCCESS);
+ }
for (;;) {
/*
@@ -788,130 +1858,96 @@ getnetgr_ldap_getent(ldap_backend_ptr be, void *a)
* processed.
* Needed for nested netgroup (memberNisNetgroup attributes).
*/
- if (p->results == NULL) {
- if ((ng = get_next_netgroup(&p->tab)) != NULL) {
- if (_ldap_filter_name(name, ng->name,
- sizeof (name)) != 0)
- break;
-
- ret = snprintf(searchfilter,
- sizeof (searchfilter),
- _F_SETMEMBER, name);
- if (ret >= sizeof (searchfilter) || ret < 0)
- break;
-
+ if (p->gnc_netgroup == NULL) {
+ if ((ngn = get_next_netgroup(&p->gnc_tab)) == NULL) {
+ /* No more netgroups to process */
#ifdef DEBUG
syslog(LOG_DEBUG, "nss_ldap: "
- "getnetgr_ldap_getent: "
- "netgroup name: %s", name);
-#endif
- ret = snprintf(userdata, sizeof (userdata),
- _F_SETMEMBER_SSD, name);
- if (ret >= sizeof (userdata) || ret < 0)
- break;
-
- result = NULL;
- rc = __ns_ldap_list(_NETGROUP, searchfilter,
- _merge_SSD_filter, netgrent_attrs, NULL,
- 0, &result, &error, NULL, userdata);
- (void) __ns_ldap_freeError(&error);
-
- if (rc == NS_LDAP_SUCCESS && result != NULL) {
- p->results = result;
- } else {
-#ifdef DEBUG
- syslog(LOG_DEBUG, "nss_ldap: "
- "getnetgr_ldap_getent: "
- "__ns_ldap_list() returned %d "
- "(result: 0x%x)", rc, result);
+ "getnetgr_ldap_getent: no more netgroup "
+ "to process.\n");
#endif
- /*
- * Will exit when no more netgroup
- * to search and no more p->results
- * to process.
- */
- (void) __ns_ldap_freeResult(&result);
- }
- } else { /* no more netgroup to process */
+ break; /* from loop */
+ }
+
+ switch (netgroup_get(ngn->ngn_name, &p->gnc_netgroup)) {
+ case NSS_SUCCESS:
+ break; /* from switch */
+ case NSS_TRYAGAIN:
+ return (NSS_TRYAGAIN);
+ default:
/*
- * If no more results to process, and since
- * there's no more netgroup to process either,
- * then it's time to break and exit the for
- * loop.
+ * Likely a nested netgroup that doesn't exist,
+ * but there may be more to try.
*/
-#ifdef DEBUG
- syslog(LOG_DEBUG, "nss_ldap: "
- "getnetgr_ldap_getent: no more netgroup "
- "to process, p->results: 0x%x",
- p->results);
-#endif
- if (p->results == NULL)
- break;
+ continue;
}
+
+ p->gnc_entry = NULL;
}
- if (p->results == NULL)
- continue;
- if (p->entry == NULL)
- p->entry = p->results->entry;
+ results = p->gnc_netgroup->ng_result;
- if (p->entry == NULL)
+ /* Empty or missing netgroup */
+ if (results == NULL) {
continue;
+ }
- if (p->attrs == NULL) {
- attrs = __ns_ldap_getAttr(p->entry, _N_TRIPLE);
- if (attrs != NULL && *attrs != NULL)
- p->attrs = attrs;
+ if (p->gnc_entry == NULL) {
+ p->gnc_entry = results->entry;
+ if (p->gnc_entry == NULL) {
+ continue;
+ }
}
- if (p->attrs != NULL) {
- attrs = p->attrs;
- buffer = args->buffer;
+ if (p->gnc_curtriple < p->gnc_netgroup->ng_triplecnt) {
+ ngc_triple_t *ngt;
+ char *buffer = args->buffer;
+ size_t left = args->buflen;
- if (strlcpy(buffer, *attrs, args->buflen) >=
- args->buflen) {
+ ngt = &p->gnc_netgroup->ng_triples[p->gnc_curtriple];
+ p->gnc_curtriple++;
+
+ /*
+ * The triple (ngt) may be freed before args->retp[] are
+ * consumed. Copy the components from the cache into
+ * args->buffer.
+ */
+ if (set_retbuf(ngt->ngt_host, &buffer, &left,
+ &args->retp[NSS_NETGR_MACHINE]) != 0 ||
+ set_retbuf(ngt->ngt_user, &buffer, &left,
+ &args->retp[NSS_NETGR_USER]) != 0 ||
+ set_retbuf(ngt->ngt_domain, &buffer, &left,
+ &args->retp[NSS_NETGR_DOMAIN]) != 0) {
status = NSS_STR_PARSE_ERANGE;
break;
}
+ args->status = NSS_NETGR_FOUND;
- rc = split_triple(buffer, &hostname, &username,
- &domain);
- attrs++;
- if (attrs != NULL && *attrs != NULL)
- p->attrs = attrs;
- else
- p->attrs = NULL;
- if (rc == 0) {
- args->retp[NSS_NETGR_MACHINE] = hostname;
- args->retp[NSS_NETGR_USER] = username;
- args->retp[NSS_NETGR_DOMAIN] = domain;
- args->status = NSS_NETGR_FOUND;
#ifdef DEBUG
- syslog(LOG_DEBUG, "nss_ldap: "
- "getnetgr_ldap_getent: found triple "
- "(%s, %s, %s), 0x%x to process",
- hostname ? hostname : "",
- username ? username : "",
- domain ? domain : "",
- p->attrs);
+ syslog(LOG_DEBUG, "nss_ldap: getnetgr_ldap_getent: "
+ "found triple (%s,%s,%s), %d more to process",
+ args->retp[NSS_NETGR_MACHINE] ?
+ args->retp[NSS_NETGR_MACHINE] : "",
+ args->retp[NSS_NETGR_USER] ?
+ args->retp[NSS_NETGR_USER] : "",
+ args->retp[NSS_NETGR_DOMAIN] ?
+ args->retp[NSS_NETGR_DOMAIN] : "",
+ p->gnc_netgroup->ng_triplecnt - p->gnc_curtriple);
#endif
- if (p->attrs != NULL)
- break;
- }
+ break;
}
- if (p->attrs == NULL) {
- rc = add_netgroup_member_entry(p->entry, &p->tab);
- if (rc != 0) {
- args->status = NSS_NETGR_NO;
- break;
- }
+ /* Despite its name, this adds all members on this entry. */
+ if (add_netgroup_member_entry(p->gnc_entry, &p->gnc_tab) != 0) {
+ args->status = NSS_NETGR_NO;
+ break;
+ }
- p->entry = p->entry->next;
- if (p->entry == NULL)
- (void) __ns_ldap_freeResult(&p->results);
- if (args->status == NSS_NETGR_FOUND)
- break;
+ p->gnc_entry = p->gnc_entry->next;
+ if (p->gnc_entry == NULL) {
+ netgroup_rele(p->gnc_netgroup);
+ p->gnc_netgroup = NULL;
+ p->gnc_curtriple = 0;
}
}
@@ -921,14 +1957,13 @@ getnetgr_ldap_getent(ldap_backend_ptr be, void *a)
static ldap_backend_op_t getnetgroup_ops[] = {
getnetgr_ldap_destr,
getnetgr_ldap_endent,
- getnetgr_ldap_setent,
+ NULL,
getnetgr_ldap_getent,
};
/*
- *
+ * setnetgrent() backend, at least for non-nscd case.
*/
-
static nss_status_t
netgr_set(ldap_backend_ptr be, void *a)
{
@@ -938,8 +1973,8 @@ netgr_set(ldap_backend_ptr be, void *a)
getnetgrent_cookie_t *p;
#ifdef DEBUG
- (void) fprintf(stdout, "\n[getnetgrent.c: netgr_set]\n");
- (void) fprintf(stdout,
+ (void) fprintf(stderr, "\n[getnetgrent.c: netgr_set]\n");
+ (void) fprintf(stderr,
"\targs->netgroup: %s\n", ISNULL(args->netgroup));
#endif /* DEBUG */
@@ -950,12 +1985,12 @@ netgr_set(ldap_backend_ptr be, void *a)
p = (getnetgrent_cookie_t *)calloc(1, sizeof (getnetgrent_cookie_t));
if (p == NULL)
return ((nss_status_t)NSS_NOTFOUND);
- p->netgroup = strdup(args->netgroup);
- if (p->netgroup == NULL) {
+ p->gnc_name = strdup(args->netgroup);
+ if (p->gnc_name == NULL) {
free(p);
return ((nss_status_t)NSS_NOTFOUND);
}
- if (add_netgroup_name(args->netgroup, &p->tab) == -1) {
+ if (add_netgroup_name(args->netgroup, &p->gnc_tab) == -1) {
free_getnetgrent_cookie(&p);
return ((nss_status_t)NSS_NOTFOUND);
}
@@ -976,11 +2011,275 @@ netgr_set(ldap_backend_ptr be, void *a)
get_be->netgroup_cookie = p;
args->iterator = (nss_backend_t *)get_be;
- (void) __ns_ldap_freeResult(&be->result);
-
return (NSS_SUCCESS);
}
+/*
+ * Initialization and configuration
+ */
+
+static int
+ngc_compare(const void *l, const void *r)
+{
+ const netgroup_t *ngl = l, *ngr = r;
+ int ret;
+
+ ret = strcmp(ngl->ng_name, ngr->ng_name);
+ if (ret < 0)
+ return (-1);
+ if (ret > 0)
+ return (1);
+ return (0);
+}
+
+static int
+yntoi(const char *yorn, int minval __unused, int maxval __unused,
+ int *retval, char *errbuf, size_t errbufsz)
+{
+ if (strcasecmp(yorn, "yes") == 0) {
+ *retval = 1;
+ return (0);
+ }
+ if (strcasecmp(yorn, "no") == 0) {
+ *retval = 0;
+ return (0);
+ }
+ (void) snprintf(errbuf, errbufsz,
+ "invalid value '%s': expected 'yes' or 'no'", yorn);
+ return (-1);
+}
+
+static int
+safestrtoi(const char *str, int minval, int maxval, int *retval,
+ char *errbuf, size_t errbufsz)
+{
+ long val;
+ char *end;
+
+ errno = 0;
+ val = strtol(str, &end, 10);
+ if (errno != 0 || *end != '\0' || val < minval || val > maxval) {
+ (void) snprintf(errbuf, errbufsz,
+ "invalid value '%s': expected integer between %d and %d",
+ str, minval, maxval);
+ return (-1);
+ }
+ *retval = (int)val;
+ return (0);
+}
+
+/*
+ * nscd does not provide us with the config or an easy way to get it. We'll
+ * fetch it ourselves.
+ */
+static void
+read_nscd_conf(void)
+{
+ const int week = 60 * 60 * 24 * 7;
+ struct {
+ char *key;
+ int *valp;
+ int defval;
+ int minval;
+ int maxval;
+ int (*toi)(const char *, int, int, int *, char *, size_t);
+ } config[] = {
+ { "enable-cache", (int *)&ngc_enable, 1, 0, 1, yntoi },
+ { "positive-time-to-live", &ngc_pos_ttl, NGC_POS_TTL, 0, week,
+ safestrtoi },
+ { "negative-time-to-live", &ngc_neg_ttl, NGC_NEG_TTL, 0, week,
+ safestrtoi }
+ };
+ uint32_t i;
+ FILE *cfg;
+ char buf[1024];
+ uint32_t line = 0;
+
+ /*
+ * Set values back to their defaults in case they were removed from
+ * nscd.conf.
+ */
+ for (i = 0; i < ARRAY_SIZE(config); i++) {
+ *config[i].valp = config[i].defval;
+ };
+
+ if ((cfg = fopen(NSCD_CONF, "rF")) == NULL) {
+ syslog(LOG_ERR, "nss_ldap: unable to read nscd.conf: %m");
+ return;
+ }
+
+ while (fgets(buf, sizeof (buf), cfg) != NULL) {
+ char *key, *db, *strval, *junk, *last = NULL;
+ char errmsg[1024];
+
+ line++;
+
+ if ((key = strchr(buf, '#')) != NULL) {
+ *key = '\0';
+ }
+ if ((key = strtok_r(buf, "\n\t ", &last)) == NULL) {
+ continue;
+ }
+ if ((db = strtok_r(NULL, "\n\t ", &last)) == NULL) {
+ continue;
+ }
+ if (strcmp(db, "netgroup") != 0) {
+ continue;
+ }
+
+ strval = strtok_r(NULL, "\n\t ", &last);
+ junk = strtok_r(NULL, "\n\t ", &last);
+
+ for (i = 0; i < ARRAY_SIZE(config); i++) {
+ if (strcmp(config[i].key, key) == 0) {
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(config)) {
+ syslog(LOG_ERR, "nss_ldap: %s:%d: "
+ "netgroup attribute '%s' invalid", NSCD_CONF,
+ line, key);
+ continue;
+ }
+ if (strval == NULL) {
+ syslog(LOG_ERR, "nss_ldap: %s:%d: "
+ "netgroup attribute '%s' missing value", NSCD_CONF,
+ line, key);
+ continue;
+ }
+ if (junk != NULL) {
+ syslog(LOG_ERR, "nss_ldap: %s:%d "
+ "netgroup attribute '%s' has too many values",
+ NSCD_CONF, line, key);
+ continue;
+ }
+ if (config[i].toi(strval, config[i].minval, config[i].maxval,
+ config[i].valp, errmsg, sizeof (errmsg)) != 0) {
+ syslog(LOG_ERR, "nss_ldap: %s:%d: %s", NSCD_CONF, line,
+ key, errmsg);
+ continue;
+ }
+ }
+ VERIFY0(fclose(cfg));
+}
+
+static void
+ngc_init(void)
+{
+ char fmri[sizeof (NSCD_FMRI) + 1]; /* space for extra char */
+ scf_handle_t *scf = NULL;
+
+ mutex_enter(&ngc_lock);
+
+ DTRACE_PROBE1(nss_ldap, ngc__init, ngc_initialized);
+
+ VERIFY0(ngc_initialized);
+
+ /* See ngc_time() */
+ ngc_first_tick = NSEC2SEC(gethrtime());
+
+ read_nscd_conf();
+
+ /*
+ * Even when not caching, the caching structures are used - netgroups
+ * just expire immediately and are pruned when ng_refcnt drops to zero.
+ */
+ avl_create(&ngc_cache, ngc_compare, sizeof (netgroup_t),
+ offsetof(netgroup_t, ng_linkage));
+ list_create(&ngc_graveyard, sizeof (netgroup_t),
+ offsetof(netgroup_t, ng_linkage));
+ list_create(&ngc_neg_expire_queue, sizeof (netgroup_t),
+ offsetof(netgroup_t, ng_expire_linkage));
+ list_create(&ngc_pos_expire_queue, sizeof (netgroup_t),
+ offsetof(netgroup_t, ng_expire_linkage));
+
+ /*
+ * Name service backends may run under nscd or as part of some other
+ * process that is making a request. Keep things as light as possible
+ * while not running under nscd.
+ */
+ if (ngc_enable && (scf = scf_handle_create(SCF_VERSION)) != NULL &&
+ scf_handle_bind(scf) == 0 &&
+ scf_myname(scf, fmri, sizeof (fmri)) == (sizeof (NSCD_FMRI) - 1) &&
+ strcmp(fmri, NSCD_FMRI) == 0) {
+ char *env;
+
+ /* For testing */
+ if ((env = getenv("NSS_LDAP_REAP_INTERVAL")) != NULL) {
+ ngc_reap_interval = atoi(env);
+ VERIFY3S(ngc_reap_interval, >, 0);
+ }
+
+ ngc_warmer_die = B_FALSE;
+ list_create(&ngc_warm_queue, sizeof (netgroup_t),
+ offsetof(netgroup_t, ng_warm_linkage));
+ VERIFY0(cond_init(&ngc_warm_cv, USYNC_THREAD, NULL));
+ if (thr_create(NULL, 0, ngc_warmer, NULL, 0,
+ &ngc_warmer_tid) != 0) {
+ ngc_warmer_tid = 0;
+ }
+ } else {
+ ngc_enable = B_FALSE;
+ ngc_warmer_tid = 0;
+ }
+ if (scf != NULL) {
+ scf_handle_destroy(scf);
+ }
+
+ ngc_initialized = B_TRUE;
+ mutex_exit(&ngc_lock);
+}
+
+/*
+ * This performs an orderly cleanup when the nss_ldap is unloaded. The name
+ * service switch (with nscd or arbitrary libc consumer) doesn't intend for
+ * backends to keep state, so we rely on a little help from the dynamic linker
+ * on unload.
+ */
+#pragma fini(ngc_fini)
+void
+ngc_fini(void)
+{
+ netgroup_t *ng, *next;
+
+ mutex_enter(&ngc_lock);
+
+ DTRACE_PROBE1(nss_ldap, ngc__fini, ngc_initialized);
+
+ if (!ngc_initialized || !ngc_enable) {
+ mutex_exit(&ngc_lock);
+ return;
+ }
+
+ ngc_initialized = B_FALSE;
+
+ if (ngc_warmer_tid != 0) {
+ ngc_warmer_die = B_TRUE;
+ cond_signal(&ngc_warm_cv);
+
+ mutex_exit(&ngc_lock);
+ (void) thr_join(ngc_warmer_tid, NULL, NULL);
+ mutex_enter(&ngc_lock);
+ }
+
+ for (ng = avl_first(&ngc_cache); ng != NULL; ng = next) {
+ next = AVL_NEXT(&ngc_cache, ng);
+ ng->ng_refcnt = 0;
+ ngc_dispose_locked(ng);
+ }
+ avl_destroy(&ngc_cache);
+ list_destroy(&ngc_neg_expire_queue);
+ list_destroy(&ngc_pos_expire_queue);
+
+ for (ng = list_head(&ngc_graveyard); ng != NULL; ng = next) {
+ next = list_next(&ngc_graveyard, ng);
+ ng->ng_refcnt = 0;
+ ngc_dispose_locked(ng);
+ }
+ list_destroy(&ngc_graveyard);
+
+ mutex_exit(&ngc_lock);
+}
/*ARGSUSED1*/
static nss_status_t
@@ -988,7 +2287,7 @@ netgr_ldap_destr(ldap_backend_ptr be, void *a)
{
#ifdef DEBUG
- (void) fprintf(stdout, "\n[getnetgrent.c: netgr_ldap_destr]\n");
+ (void) fprintf(stderr, "\n[getnetgrent.c: netgr_ldap_destr]\n");
#endif /* DEBUG */
(void) _clean_ldap_backend(be);
@@ -997,8 +2296,6 @@ netgr_ldap_destr(ldap_backend_ptr be, void *a)
}
-
-
static ldap_backend_op_t netgroup_ops[] = {
netgr_ldap_destr,
0,
@@ -1022,10 +2319,13 @@ _nss_ldap_netgroup_constr(const char *dummy1, const char *dummy2,
{
#ifdef DEBUG
- (void) fprintf(stdout,
+ (void) fprintf(stderr,
"\n[getnetgrent.c: _nss_ldap_netgroup_constr]\n");
#endif /* DEBUG */
+ /* Initialize the cache. */
+ ngc_init();
+
return ((nss_backend_t *)_nss_ldap_constr(netgroup_ops,
sizeof (netgroup_ops)/sizeof (netgroup_ops[0]), _NETGROUP,
netgrent_attrs, NULL));
diff --git a/usr/src/lib/nsswitch/ldap/common/getspent.c b/usr/src/lib/nsswitch/ldap/common/getspent.c
index bcdba2998c..1557d5e550 100644
--- a/usr/src/lib/nsswitch/ldap/common/getspent.c
+++ b/usr/src/lib/nsswitch/ldap/common/getspent.c
@@ -21,6 +21,7 @@
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
+ * Copyright 2019 Joyent, Inc.
*/
#include <shadow.h>
@@ -77,8 +78,10 @@ _nss_ldap_shadow2str(ldap_backend_ptr be, nss_XbyY_args_t *argp)
ns_ldap_result_t *result = be->result;
char **uid, **passwd, **last, **smin, **smax;
char **warning, **inactive, **expire, **flag;
- char *last_str, *min_str, *max_str, *warning_str;
- char *inactive_str, *expire_str, *flag_str;
+ char *last_str = _NO_VALUE, *min_str = _NO_VALUE;
+ char *max_str = _NO_VALUE, *warning_str = _NO_VALUE;
+ char *inactive_str = _NO_VALUE, *expire_str = _NO_VALUE;
+ char *flag_str = _NO_VALUE;
if (result == NULL)
return (NSS_STR_PARSE_PARSE);
diff --git a/usr/src/lib/nsswitch/ldap/common/ldap_common.h b/usr/src/lib/nsswitch/ldap/common/ldap_common.h
index 690dd15adc..1a8fe55dd9 100644
--- a/usr/src/lib/nsswitch/ldap/common/ldap_common.h
+++ b/usr/src/lib/nsswitch/ldap/common/ldap_common.h
@@ -20,6 +20,7 @@
*/
/*
* Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright 2019 Joyent, Inc.
*/
#ifndef _LDAP_COMMON_H
@@ -70,8 +71,7 @@ extern "C" {
#define NSS_STR_PARSE_NO_ADDR (NSS_STR_PARSE_ERANGE + 100)
#define NSS_STR_PARSE_NO_RESULT (NSS_STR_PARSE_ERANGE + 101)
-#define DOTTEDSUBDOMAIN(string) \
- ((string != NULL) && (strchr(string, '.') != NULL))
+#define DOTTEDSUBDOMAIN(string) (strchr(string, '.') != NULL)
#define SEARCHFILTERLEN 256
#define _NO_VALUE ""
diff --git a/usr/src/lib/nsswitch/ldap/common/provider.d b/usr/src/lib/nsswitch/ldap/common/provider.d
new file mode 100644
index 0000000000..1f5a1f271e
--- /dev/null
+++ b/usr/src/lib/nsswitch/ldap/common/provider.d
@@ -0,0 +1,50 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019 Joyent, Inc.
+ */
+
+provider nss_ldap {
+ /* netgroup-* probes all start with string, netgroup_t */
+ probe netgroup__cache__add__collision(string, uintptr_t, uintptr_t);
+ probe netgroup__cache__add(string, uintptr_t, int);
+ probe netgroup__cache__dispose(string, uintptr_t, int, int);
+ probe netgroup__cache__hold(string, uintptr_t, int);
+ probe netgroup__cache__free(string, uintptr_t);
+ probe netgroup__cache__rele(string, uintptr_t, int);
+ probe netgroup__cache__to__graveyard(string, uintptr_t);
+ probe netgroup__cache__triple(string, uintptr_t, string, string,
+ string);
+ probe netgroup__get__from__cache(string, uintptr_t, int);
+ probe netgroup__reap(string, uintptr_t, uintptr_t);
+ probe netgroup__warmer__backwards(string, uintptr_t);
+ probe netgroup__warmer__enqueue(string, uintptr_t);
+ probe netgroup__warmer__expire(string, uintptr_t);
+ probe netgroup__warmer__ldap__fail(string, uintptr_t);
+ probe netgroup__warmer__no__stamp(string, uintptr_t);
+ probe netgroup__warmer__reload__fail(string, uintptr_t, int);
+ probe netgroup__warmer__reload__success(string, uintptr_t, uintptr_t);
+ probe netgroup__warmer__renewal(string, uintptr_t);
+ probe netgroup__warmer__resurrection(string, uintptr_t);
+
+ /* probes not starting with netgroup-* can be more diverse */
+ probe innetgr(string, string, string, string, int);
+ probe ngc__fini(int);
+ probe ngc__init(int);
+ probe ngc__tick(uint32_t);
+};
+
+#pragma D attributes Evolving/Evolving/Common provider nss_ldap provider
+#pragma D attributes Private/Private/Unknown provider nss_ldap module
+#pragma D attributes Private/Private/Unknown provider nss_ldap function
+#pragma D attributes Evolving/Evolving/Common provider nss_ldap name
+#pragma D attributes Evolving/Evolving/Common provider nss_ldap args
diff --git a/usr/src/lib/nsswitch/ldap/i386/Makefile b/usr/src/lib/nsswitch/ldap/i386/Makefile
index ee9fdcccc5..b947d468f2 100644
--- a/usr/src/lib/nsswitch/ldap/i386/Makefile
+++ b/usr/src/lib/nsswitch/ldap/i386/Makefile
@@ -19,11 +19,9 @@
#
# CDDL HEADER END
#
-#
-#ident "%Z%%M% %I% %E% SMI"
-#
# Copyright (c) 1999,2001 by Sun Microsystems, Inc.
# All rights reserved.
+# Copyright 2019 Joyent, Inc.
#
# lib/nsswitch/ldap/i386/Makefile
@@ -31,6 +29,6 @@ include ../Makefile.com
LIBS = $(DYNLIB1)
-include ../../Makefile.targ
+include ../Makefile.targ
install: all $(ROOTLIBS)
diff --git a/usr/src/lib/nsswitch/ldap/sparc/Makefile b/usr/src/lib/nsswitch/ldap/sparc/Makefile
index a87617b860..eea0b48f93 100644
--- a/usr/src/lib/nsswitch/ldap/sparc/Makefile
+++ b/usr/src/lib/nsswitch/ldap/sparc/Makefile
@@ -19,11 +19,9 @@
#
# CDDL HEADER END
#
-#
-#ident "%Z%%M% %I% %E% SMI"
-#
# Copyright (c) 1999,2001 by Sun Microsystems, Inc.
# All rights reserved.
+# Copyright 2019 Joyent, Inc.
#
# lib/nsswitch/ldap/sparc/Makefile
@@ -31,6 +29,6 @@ include ../Makefile.com
LIBS = $(DYNLIB1)
-include ../../Makefile.targ
+include ../Makefile.targ
install: all $(ROOTLIBS)
diff --git a/usr/src/lib/nsswitch/ldap/sparcv9/Makefile b/usr/src/lib/nsswitch/ldap/sparcv9/Makefile
index 938a17187a..c38b660d8b 100644
--- a/usr/src/lib/nsswitch/ldap/sparcv9/Makefile
+++ b/usr/src/lib/nsswitch/ldap/sparcv9/Makefile
@@ -19,11 +19,9 @@
#
# CDDL HEADER END
#
-#
-#ident "%Z%%M% %I% %E% SMI"
-#
# Copyright (c) 1999,2001 by Sun Microsystems, Inc.
# All rights reserved.
+# Copyright 2019 Joyent, Inc.
#
# lib/nsswitch/ldap/sparcv9/Makefile
@@ -32,6 +30,6 @@ include $(SRC)/lib/Makefile.lib.64
LIBS = $(DYNLIB1)
-include ../../Makefile.targ
+include ../Makefile.targ
install: all $(ROOT64DYNLIB)
diff --git a/usr/src/lib/nsswitch/nis/Makefile.com b/usr/src/lib/nsswitch/nis/Makefile.com
index cbccdfb764..8e87e78e0d 100644
--- a/usr/src/lib/nsswitch/nis/Makefile.com
+++ b/usr/src/lib/nsswitch/nis/Makefile.com
@@ -21,6 +21,7 @@
#
# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
+# Copyright 2019 Joyent, Inc.
#
LIBRARY = libnss_nis.a
@@ -57,5 +58,9 @@ include ../../../Makefile.rootfs
LINTFLAGS += -erroff=E_GLOBAL_COULD_BE_STATIC2
LINTFLAGS64 += -erroff=E_GLOBAL_COULD_BE_STATIC2
+pics/getgrent.o := CERRWARN += -_gcc=-Wno-switch
+pics/getnetgrent.o := CERRWARN += -_gcc=-Wno-parentheses
+pics/nis_common.o := CERRWARN += -_gcc=-Wno-uninitialized
+
LDLIBS += -lnsl
DYNLIB1 = nss_nis.so$(VERS)
diff --git a/usr/src/man/man4/nscd.conf.4 b/usr/src/man/man4/nscd.conf.4
index be0b415b61..9cba157f07 100644
--- a/usr/src/man/man4/nscd.conf.4
+++ b/usr/src/man/man4/nscd.conf.4
@@ -1,9 +1,10 @@
'\" te
.\" Copyright (c) 2004 Sun Microsystems, Inc. All Rights Reserved
+.\" Copyright 2019 Joyent, Inc.
.\" 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]
-.TH NSCD.CONF 4 "Mar 6, 2017"
+.TH NSCD.CONF 4 "Apr 10, 2019"
.SH NAME
nscd.conf \- name service cache daemon configuration
.SH SYNOPSIS
@@ -25,8 +26,8 @@ not interpreted by \fBnscd\fR.
\fIcachename\fR is represented by \fBhosts\fR, \fBipnodes\fR, \fBpasswd\fR,
\fBgroup\fR, \fBexec_attr\fR, \fBprof_attr\fR, \fBuser_attr\fR, \fBethers\fR,
\fBrpc\fR, \fBprotocols\fR, \fBnetworks\fR, \fBbootparams\fR,
-\fBauth_attr\fR, \fBservices\fR, \fBnetmasks\fR, \fBprinters\fR, or
-\fBproject\fR.
+\fBauth_attr\fR, \fBservices\fR, \fBnetmasks\fR, \fBprinters\fR,
+\fBproject\fR, or \fBnetgroup\fR.
.sp
.LP
The \fIattribute\fR field supports the following:
diff --git a/usr/src/test/Makefile b/usr/src/test/Makefile
index fa57d36772..49cc6f1913 100644
--- a/usr/src/test/Makefile
+++ b/usr/src/test/Makefile
@@ -12,6 +12,7 @@
#
# Copyright (c) 2012 by Delphix. All rights reserved.
# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+# Copyright 2019 Joyent, Inc.
#
.PARALLEL: $(SUBDIRS)
@@ -20,6 +21,7 @@ SUBDIRS = \
crypto-tests \
elf-tests \
libc-tests \
+ nsswitch-tests \
os-tests \
smbclient-tests \
test-runner \
diff --git a/usr/src/test/nsswitch-tests/Makefile b/usr/src/test/nsswitch-tests/Makefile
new file mode 100644
index 0000000000..6669972e13
--- /dev/null
+++ b/usr/src/test/nsswitch-tests/Makefile
@@ -0,0 +1,20 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2015 Nexenta Systems, Inc. All rights reserved.
+#
+
+.PARALLEL: $(SUBDIRS)
+
+SUBDIRS = cmd doc runfiles tests
+
+include $(SRC)/test/Makefile.com
diff --git a/usr/src/test/nsswitch-tests/runfiles/Makefile b/usr/src/test/nsswitch-tests/runfiles/Makefile
new file mode 100644
index 0000000000..6f88c57b27
--- /dev/null
+++ b/usr/src/test/nsswitch-tests/runfiles/Makefile
@@ -0,0 +1,41 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2012 by Delphix. All rights reserved.
+# Copyright 2014, OmniTI Computer Consulting, Inc. All rights reserved.
+# Copyright 2014 Garrett D'Amore <garrett@damore.org>
+# Copyright 2019 Joyent, Inc.
+#
+
+include $(SRC)/Makefile.master
+
+SRCS = default.run
+
+ROOTOPTPKG = $(ROOT)/opt/nsswitch-tests
+RUNFILES = $(ROOTOPTPKG)/runfiles
+
+CMDS = $(SRCS:%=$(RUNFILES)/%)
+$(CMDS) := FILEMODE = 0444
+
+all: $(SRCS)
+
+install: $(CMDS)
+
+clean lint clobber:
+
+$(CMDS): $(RUNFILES) $(SRCS)
+
+$(RUNFILES):
+ $(INS.dir)
+
+$(RUNFILES)/%: %
+ $(INS.file)
diff --git a/usr/src/test/nsswitch-tests/runfiles/default.run b/usr/src/test/nsswitch-tests/runfiles/default.run
new file mode 100644
index 0000000000..b9e4303edd
--- /dev/null
+++ b/usr/src/test/nsswitch-tests/runfiles/default.run
@@ -0,0 +1,26 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2018, Richard Lowe.
+# Copyright 2019 Joyent, Inc.
+#
+
+[DEFAULT]
+pre =
+verbose = False
+quiet = False
+timeout = 60
+post =
+outputdir = /var/tmp/test_results
+
+[/opt/nsswitch-tests/tests/ldap]
+tests = ['generalized_time']
diff --git a/usr/src/test/nsswitch-tests/tests/Makefile b/usr/src/test/nsswitch-tests/tests/Makefile
new file mode 100644
index 0000000000..a04b6078f0
--- /dev/null
+++ b/usr/src/test/nsswitch-tests/tests/Makefile
@@ -0,0 +1,20 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright (c) 2012, 2016 by Delphix. All rights reserved.
+# Copyright 2019 Joyent, Inc.
+#
+
+SUBDIRS = \
+ ldap
+
+include $(SRC)/test/Makefile.com
diff --git a/usr/src/test/nsswitch-tests/tests/ldap/Makefile b/usr/src/test/nsswitch-tests/tests/ldap/Makefile
new file mode 100644
index 0000000000..37b0a275f9
--- /dev/null
+++ b/usr/src/test/nsswitch-tests/tests/ldap/Makefile
@@ -0,0 +1,75 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+# Copyright 2019 Joyent, Inc.
+
+include $(SRC)/cmd/Makefile.cmd
+include $(SRC)/test/Makefile.com
+
+PROG1 = generalized_time
+OBJS1 = $(PROG1).o ldap_common.o ldap_utils.o getservent.o list.o
+
+ROOTOPTPKG = $(ROOT)/opt/nsswitch-tests
+TESTDIR = $(ROOTOPTPKG)/tests/ldap
+PROGS = $(PROG1)
+CMDS = $(PROGS:%=$(TESTDIR)/%)
+
+CPPFLAGS = -I$(SRC)/lib/nsswitch/ldap/common -I$(SRC)/lib/libsldap/common
+LDFLAGS = -lsldap -lnsl -lldap -lavl -lscf
+USDT_OBJS = provider.o
+$(PROG1) := TARGET = $(PROG1)
+
+CLEANFILES += $(PROGS) $(USDT_OBJS)
+CLEANFILES += $(OBJS1)
+CLOBBERFILES += $(CMDS)
+
+.KEEP_STATE:
+
+all: $(PROGS)
+
+test: all
+ for prog in $(PROGS); do ./$$prog; done
+
+clean:
+ $(RM) $(CLEANFILES)
+
+lint:
+
+install: all $(TESTDIR) $(CMDS)
+
+$(PROG1): $(OBJS1) .WAIT $(USDT_OBJS)
+ $(LINK.c) $(OBJS1) $(USDT_OBJS) -o $@
+ $(POST_PROCESS)
+ $(RM) $(USDT_OBJS)
+
+%.o: $(SRC)/test/nsswitch-tests/ldap/common/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
+%.o: $(SRC)/common/list/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
+%.o: $(SRC)/lib/nsswitch/ldap/common/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+ $(RM) $(USDT_OBJS)
+
+%.o: $(SRC)/lib/nsswitch/ldap/common/%.d
+ $(COMPILE.d) -o $@ -s $< $(TARGET).o
+
+$(TESTDIR):
+ $(INS.dir)
+
+$(TESTDIR)/%: %
+ $(INS.file)
+
+include $(SRC)/cmd/Makefile.targ
diff --git a/usr/src/test/nsswitch-tests/tests/ldap/generalized_time.c b/usr/src/test/nsswitch-tests/tests/ldap/generalized_time.c
new file mode 100644
index 0000000000..00a11c5af7
--- /dev/null
+++ b/usr/src/test/nsswitch-tests/tests/ldap/generalized_time.c
@@ -0,0 +1,241 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019 Joyent, Inc.
+ */
+
+#define DEBUG 1
+#include "../common/getnetgrent.c"
+
+struct {
+ const char *sval;
+ uint64_t ival;
+} pos_tests[] = {
+ { "Unspecified seconds and/or minutes; no time zone implies UTC", 0 },
+ { "2019010100", 15463008000ULL },
+ { "201901010000", 15463008000ULL },
+ { "20190101000000", 15463008000ULL },
+
+ { "As above, but explicit UTC", 0 },
+ { "2019010100Z", 15463008000ULL },
+ { "201901010000Z", 15463008000ULL },
+ { "20190101000000Z", 15463008000ULL },
+
+ { "UTC by many names", 0 },
+ { "20190415181957", 15553523970ULL },
+ { "20190415181957Z", 15553523970ULL },
+ { "20190415181957+00", 15553523970ULL },
+ { "20190415181957+0000", 15553523970ULL },
+ { "20190415181957-00", 15553523970ULL },
+ { "20190415181957-0000", 15553523970ULL },
+
+ /*
+ * Test data can be generated with gnu date on a system with a proper
+ * set of zoneinfo files.
+ *
+ * $ env TZ=Pacific/Chatham date -d @1555332597 \
+ * '+{ "%Y%m%d%H%M%S%z", %s0ULL },'
+ * { "20190415221957+0930", 15553325970ULL },
+ *
+ * The @<secs> is 1/10 of the value in the second column. Time zones to
+ * give reasonable coverage include the following. DST may cause you to
+ * get different offsets with different days.
+ *
+ * +1245 Pacific/Chatham
+ * +0930 Australia/Adelaide
+ * +0530 Asia/Kolkata
+ * +0100 Europe/London
+ * +0000 Atlantic/Reykjavik
+ * -0500 America/Chicago
+ * -0930 Pacific/Marquesas
+ * -1100 Pacific/Midway
+ */
+
+ { "The same time at various spots around the globe", 0 },
+ { "20190416013457+1245", 15553325970ULL },
+ { "20190415221957+0930", 15553325970ULL },
+ { "20190415181957+0530", 15553325970ULL },
+ { "20190415134957+0100", 15553325970ULL },
+ { "20190415124957+0000", 15553325970ULL },
+ { "20190415074957-0500", 15553325970ULL },
+ { "20190415031957-0930", 15553325970ULL },
+ { "20190415014957-1100", 15553325970ULL },
+
+ { "Party like it was 1999!", 0 },
+ { "20000101134500+1345", 9466848000ULL },
+ { "20000101103000+1030", 9466848000ULL },
+ { "20000101053000+0530", 9466848000ULL },
+ { "20000101000000+0000", 9466848000ULL },
+ { "20000101000000+0000", 9466848000ULL },
+ { "19991231180000-0600", 9466848000ULL },
+ { "19991231143000-0930", 9466848000ULL },
+ { "19991231130000-1100", 9466848000ULL },
+
+ { "Hour offsets", 0 },
+ { "20190415181957-0300", 15553631970ULL },
+ { "20190415181957-03", 15553631970ULL },
+ { "20190415181957+0200", 15553451970ULL },
+ { "20190415181957+02", 15553451970ULL },
+ { "2019041518-0300", 15553620000ULL },
+ { "201904151800-0300", 15553620000ULL },
+ { "20190415180000-0300", 15553620000ULL },
+
+ { "Half-hour offsets", 0 },
+ { "20190415181957-0230", 15553613970ULL },
+ { "2019041518-0230", 15553602000ULL },
+ { "201904151800-0230", 15553602000ULL },
+ { "20190415180000-0230", 15553602000ULL },
+ { "20190415181957+0530", 15553325970ULL },
+ { "2019041518+0530", 15553314000ULL },
+ { "201904151800+0530", 15553314000ULL },
+ { "20190415180000+0530", 15553314000ULL },
+
+ { "Fractional seconds without offsets", 0 },
+ { "20190415181957.0", 15553523970ULL },
+ { "20190415181957.1", 15553523971ULL },
+ { "20190415181957.2", 15553523972ULL },
+ { "20190415181957.3", 15553523973ULL },
+ { "20190415181957.4", 15553523974ULL },
+ { "20190415181957.5", 15553523975ULL },
+ { "20190415181957.6", 15553523976ULL },
+ { "20190415181957.7", 15553523977ULL },
+ { "20190415181957.8", 15553523978ULL },
+ { "20190415181957.9", 15553523979ULL },
+
+ { "Fractional minutes (0.1 minute is 6 seconds)", 0 },
+ { "201904151819.0", 15553523400ULL },
+ { "201904151819.1", 15553523460ULL },
+ { "201904151819.2", 15553523520ULL },
+ { "201904151819.3", 15553523580ULL },
+ { "201904151819.4", 15553523640ULL },
+ { "201904151819.5", 15553523700ULL },
+ { "201904151819.6", 15553523760ULL },
+ { "201904151819.7", 15553523820ULL },
+ { "201904151819.8", 15553523880ULL },
+ { "201904151819.9", 15553523940ULL },
+
+ { "Fractional hours (0.1 hour is 360 seconds)", 0 },
+ { "2019041518.0", 15553512000ULL },
+ { "2019041518.1", 15553515600ULL },
+ { "2019041518.2", 15553519200ULL },
+ { "2019041518.3", 15553522800ULL },
+ { "2019041518.4", 15553526400ULL },
+ { "2019041518.5", 15553530000ULL },
+ { "2019041518.6", 15553533600ULL },
+ { "2019041518.7", 15553537200ULL },
+ { "2019041518.8", 15553540800ULL },
+ { "2019041518.9", 15553544400ULL },
+
+ { "Fractions with offsets", 0 },
+ { "20190415181957.1-0300", 15553631971ULL },
+ { "20190415181957.2-03", 15553631972ULL },
+ { "20190415181957.3+0200", 15553451973ULL },
+ { "20190415181957.4+02", 15553451974ULL },
+ { "2019041518.5-0300", 15553638000ULL },
+ { "201904151800.5-0300", 15553620300ULL },
+ { "20190415180000.5-0300", 15553620005ULL },
+
+ { "Fractions with commas", 0 },
+ { "20190415181957,3", 15553523973ULL },
+ { "201904151819,7", 15553523820ULL },
+ { "2019041518,4", 15553526400ULL },
+ { "20190415181957,1-0300", 15553631971ULL },
+};
+
+
+struct {
+ const char *sval;
+ const char *desc;
+} neg_tests[] = {
+ { "1969123100", "Before the beginning of time" },
+ { "2019131518", "Invalid month" },
+ { "2019020018", "Invalid day (00); parse fails" },
+ { "2019013218", "Invalid day (Jan 32); parse fails" },
+ { "2019040124", "Invalid hour (24); parse fails" },
+ { "201904012360", "Invalid minute (60); parse fails" },
+ { "20190401233061" "Invalid second (61); parse fails" },
+ { "20190416013457+24", "Offset hours too large" },
+ { "20190416013457+0160", "Offset minutes too large" },
+ { "201904151819.10", "Too many fractional digits" },
+ { "2019041518.8.8", "Too many decimals" },
+ { " 2019041518", "Leading white space" },
+ { "2019041518 ", "Trailing white space" },
+ { "2019041518ZZ", "Zulu Zulu" },
+ { "2019041518z", "Not the zed you are looking for" },
+ { "2020-01-04T14:23Z", "Illegal characters (hyphen)" },
+ { "20200104T14:23Z", "Illegal characters (T)" },
+ { "2020010414:23Z", "Illegal characters (colon)" },
+ { "2020010414.", "Decimal (period) with no digit" },
+ { "2020010414,", "Decimal (comma) with no digit" },
+ { "202001041", "Invalid minute (1 digit)" },
+ { "20200401120000+EDT", "Illegal timezone, not offset" },
+};
+
+int
+main(void)
+{
+ int i;
+ uint64_t when;
+ int err;
+ uint32_t pass = 0;
+ uint32_t fail = 0;
+
+ for (i = 0; i < ARRAY_SIZE(pos_tests); i++) {
+ const char *sval = pos_tests[i].sval;
+ uint64_t ival = pos_tests[i].ival;
+
+ if (ival == 0) {
+ (void) printf("\n%s\n", sval);
+ continue;
+ }
+ err = parse_generalized_time(sval, &when);
+ if (err != 0) {
+ (void) printf("*FAIL pos_tests[%d] %s parse error %d\n",
+ i, sval, err);
+ fail++;
+ continue;
+ }
+ if (when != ival) {
+ (void) printf("*FAIL pos_tests[%d] %-22s expected %llu "
+ "got %llu\n", i, sval, ival, when);
+ fail++;
+ continue;
+ }
+ (void) printf(" PASS pos_tests[%d] %-22s => %llu\n", i, sval,
+ when);
+ pass++;
+ }
+
+ (void) printf("\n\nNegative tests\n");
+ for (i = 0; i < ARRAY_SIZE(neg_tests); i++) {
+ const char *sval = neg_tests[i].sval;
+ const char *desc = neg_tests[i].desc;
+
+ when = 12345678987654321ULL;
+ err = parse_generalized_time(sval, &when);
+ if (err == 0) {
+ (void) printf("*FAIL neg_tests[%d] %s parse succeeded "
+ "(%llu): %s\n", i, sval, when, desc);
+ fail++;
+ } else {
+ (void) printf(" PASS neg_tests[%d] %s: %s\n\n",
+ i, sval, desc);
+ pass++;
+ }
+ }
+
+ printf("\nSummary:\n");
+ printf(" PASS: %3d/%d\n", pass, pass + fail);
+ printf(" FAIL: %3d/%d\n", fail, pass + fail);
+
+ return (fail == 0 ? 0 : 1);
+}