aboutsummaryrefslogtreecommitdiff
path: root/nss
diff options
context:
space:
mode:
Diffstat (limited to 'nss')
-rw-r--r--nss/Makefile2
-rw-r--r--nss/nss_database.c433
-rw-r--r--nss/nss_database.h88
3 files changed, 522 insertions, 1 deletions
diff --git a/nss/Makefile b/nss/Makefile
index f3e5550..ccd1a6a 100644
--- a/nss/Makefile
+++ b/nss/Makefile
@@ -31,7 +31,7 @@ routines = nsswitch getnssent getnssent_r digits_dots \
compat-lookup nss_hash nss_files_fopen \
nss_readline nss_parse_line_result \
nss_fgetent_r nss_module nss_action \
- nss_action_parse
+ nss_action_parse nss_database
# These are the databases that go through nss dispatch.
# Caution: if you add a database here, you must add its real name
diff --git a/nss/nss_database.c b/nss/nss_database.c
new file mode 100644
index 0000000..329bfb1
--- /dev/null
+++ b/nss/nss_database.c
@@ -0,0 +1,433 @@
+/* Mapping NSS services to action lists.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include "nss_database.h"
+
+#include <allocate_once.h>
+#include <array_length.h>
+#include <assert.h>
+#include <atomic.h>
+#include <ctype.h>
+#include <file_change_detection.h>
+#include <libc-lock.h>
+#include <netdb.h>
+#include <stdio_ext.h>
+#include <string.h>
+
+struct nss_database_state
+{
+ struct nss_database_data data;
+ __libc_lock_define (, lock);
+};
+
+
+/* Global NSS database state. Underlying type is "struct
+ nss_database_state *" but the allocate_once API requires
+ "void *". */
+static void *global_database_state;
+
+/* Allocate and return pointer to nss_database_state object or
+ on failure return NULL. */
+static void *
+global_state_allocate (void *closure)
+{
+ struct nss_database_state *result = malloc (sizeof (*result));
+ if (result != NULL)
+ {
+ result->data.nsswitch_conf.size = -1; /* Force reload. */
+ memset (result->data.services, 0, sizeof (result->data.services));
+ result->data.initialized = true;
+ result->data.reload_disabled = false;
+ __libc_lock_init (result->lock);
+ }
+ return result;
+}
+
+/* Return pointer to global NSS database state, allocating as
+ required, or returning NULL on failure. */
+static struct nss_database_state *
+nss_database_state_get (void)
+{
+ return allocate_once (&global_database_state, global_state_allocate,
+ NULL, NULL);
+}
+
+/* Database default selections. nis/compat mappings get turned into
+ "files" for !LINK_OBSOLETE_NSL configurations. */
+enum nss_database_default
+{
+ nss_database_default_defconfig = 0, /* "nis [NOTFOUND=return] files". */
+ nss_database_default_compat, /* "compat [NOTFOUND=return] files". */
+ nss_database_default_dns, /* "dns [!UNAVAIL=return] files". */
+ nss_database_default_files, /* "files". */
+ nss_database_default_nis, /* "nis". */
+ nss_database_default_nis_nisplus, /* "nis nisplus". */
+ nss_database_default_none, /* Empty list. */
+
+ NSS_DATABASE_DEFAULT_COUNT /* Number of defaults. */
+};
+
+/* Databases not listed default to nss_database_default_defconfig. */
+static const char per_database_defaults[NSS_DATABASE_COUNT] =
+ {
+ [nss_database_group] = nss_database_default_compat,
+ [nss_database_gshadow] = nss_database_default_files,
+ [nss_database_hosts] = nss_database_default_dns,
+ [nss_database_initgroups] = nss_database_default_none,
+ [nss_database_networks] = nss_database_default_dns,
+ [nss_database_passwd] = nss_database_default_compat,
+ [nss_database_publickey] = nss_database_default_nis_nisplus,
+ [nss_database_shadow] = nss_database_default_compat,
+ };
+
+struct nss_database_default_cache
+{
+ nss_action_list caches[NSS_DATABASE_DEFAULT_COUNT];
+};
+
+static bool
+nss_database_select_default (struct nss_database_default_cache *cache,
+ enum nss_database db, nss_action_list *result)
+{
+ enum nss_database_default def = per_database_defaults[db];
+ *result = cache->caches[def];
+ if (*result != NULL)
+ return true;
+
+ /* Determine the default line string. */
+ const char *line;
+ switch (def)
+ {
+#ifdef LINK_OBSOLETE_NSL
+ case nss_database_default_defconfig:
+ line = "nis [NOTFOUND=return] files";
+ break;
+ case nss_database_default_compat:
+ line = "compat [NOTFOUND=return] files";
+ break;
+#endif
+
+ case nss_database_default_dns:
+ line = "dns [!UNAVAIL=return] files";
+ break;
+
+ case nss_database_default_files:
+#ifndef LINK_OBSOLETE_NSL
+ case nss_database_default_defconfig:
+ case nss_database_default_compat:
+#endif
+ line = "files";
+ break;
+
+ case nss_database_default_nis:
+ line = "nis";
+ break;
+
+ case nss_database_default_nis_nisplus:
+ line = "nis nisplus";
+ break;
+
+ case nss_database_default_none:
+ /* Very special case: Leave *result as NULL. */
+ return true;
+
+ case NSS_DATABASE_DEFAULT_COUNT:
+ __builtin_unreachable ();
+ }
+ if (def < 0 || def >= NSS_DATABASE_DEFAULT_COUNT)
+ /* Tell GCC that line is initialized. */
+ __builtin_unreachable ();
+
+ *result = __nss_action_parse (line);
+ if (*result == NULL)
+ {
+ assert (errno == ENOMEM);
+ return false;
+ }
+ else
+ return true;
+}
+
+/* database_name must be large enough for each individual name plus a
+ null terminator. */
+typedef char database_name[11];
+#define DEFINE_DATABASE(name) \
+ _Static_assert (sizeof (#name) <= sizeof (database_name), #name);
+#include "databases.def"
+#undef DEFINE_DATABASE
+
+static const database_name nss_database_name_array[] =
+ {
+#define DEFINE_DATABASE(name) #name,
+#include "databases.def"
+#undef DEFINE_DATABASE
+ };
+
+static int
+name_search (const void *left, const void *right)
+{
+ return strcmp (left, right);
+}
+
+static int
+name_to_database_index (const char *name)
+{
+ database_name *name_entry = bsearch (name, nss_database_name_array,
+ array_length (nss_database_name_array),
+ sizeof (database_name), name_search);
+ if (name_entry == NULL)
+ return -1;
+ return name_entry - nss_database_name_array;
+}
+
+static bool
+process_line (struct nss_database_data *data, char *line)
+{
+ /* Ignore leading white spaces. ATTENTION: this is different from
+ what is implemented in Solaris. The Solaris man page says a line
+ beginning with a white space character is ignored. We regard
+ this as just another misfeature in Solaris. */
+ while (isspace (line[0]))
+ ++line;
+
+ /* Recognize `<database> ":"'. */
+ char *name = line;
+ while (line[0] != '\0' && !isspace (line[0]) && line[0] != ':')
+ ++line;
+ if (line[0] == '\0' || name == line)
+ /* Syntax error. Skip this line. */
+ return true;
+ *line++ = '\0';
+
+ int db = name_to_database_index (name);
+ if (db < 0)
+ /* Not our database e.g. sudoers, automount, etc. */
+ return true;
+
+ nss_action_list result = __nss_action_parse (line);
+ if (result == NULL)
+ return false;
+ data->services[db] = result;
+ return true;
+}
+
+/* Iterate over the lines in FP, parse them, and store them in DATA.
+ Return false on memory allocation failure, true on success. */
+static bool
+nss_database_reload_1 (struct nss_database_data *data, FILE *fp)
+{
+ char *line = NULL;
+ size_t line_allocated = 0;
+ bool result = false;
+
+ while (true)
+ {
+ ssize_t ret = __getline (&line, &line_allocated, fp);
+ if (ferror_unlocked (fp))
+ break;
+ if (feof_unlocked (fp))
+ {
+ result = true;
+ break;
+ }
+ assert (ret > 0);
+ (void) ret; /* For NDEBUG builds. */
+
+ if (!process_line (data, line))
+ break;
+ }
+
+ free (line);
+ return result;
+}
+
+static bool
+nss_database_reload (struct nss_database_data *staging,
+ struct file_change_detection *initial)
+{
+ FILE *fp = fopen (_PATH_NSSWITCH_CONF, "rce");
+ if (fp == NULL)
+ switch (errno)
+ {
+ case EACCES:
+ case EISDIR:
+ case ELOOP:
+ case ENOENT:
+ case ENOTDIR:
+ case EPERM:
+ /* Ignore these errors. They are persistent errors caused
+ by file system contents. */
+ break;
+ default:
+ /* Other errors refer to resource allocation problems and
+ need to be handled by the application. */
+ return false;
+ }
+ else
+ /* No other threads have access to fp. */
+ __fsetlocking (fp, FSETLOCKING_BYCALLER);
+
+ bool ok = true;
+ if (fp != NULL)
+ ok = nss_database_reload_1 (staging, fp);
+
+ /* Apply defaults. */
+ if (ok)
+ {
+ struct nss_database_default_cache cache = { };
+ for (int i = 0; i < NSS_DATABASE_COUNT; ++i)
+ if (staging->services[i] == NULL)
+ {
+ ok = nss_database_select_default (&cache, i,
+ &staging->services[i]);
+ if (!ok)
+ break;
+ }
+ }
+
+ if (ok)
+ ok = __file_change_detection_for_fp (&staging->nsswitch_conf, fp);
+
+ if (fp != NULL)
+ {
+ int saved_errno = errno;
+ fclose (fp);
+ __set_errno (saved_errno);
+ }
+
+ if (ok && !__file_is_unchanged (&staging->nsswitch_conf, initial))
+ /* Reload is required because the file changed while reading. */
+ staging->nsswitch_conf.size = -1;
+
+ return ok;
+}
+
+static bool
+nss_database_check_reload_and_get (struct nss_database_state *local,
+ nss_action_list *result,
+ enum nss_database database_index)
+{
+ /* Acquire MO is needed because the thread that sets reload_disabled
+ may have loaded the configuration first, so synchronize with the
+ Release MO store there. */
+ if (atomic_load_acquire (&local->data.reload_disabled))
+ /* No reload, so there is no error. */
+ return true;
+
+ struct file_change_detection initial;
+ if (!__file_change_detection_for_path (&initial, _PATH_NSSWITCH_CONF))
+ return false;
+
+ __libc_lock_lock (local->lock);
+ if (__file_is_unchanged (&initial, &local->data.nsswitch_conf))
+ {
+ /* Configuration is up-to-date. Read it and return it to the
+ caller. */
+ *result = local->data.services[database_index];
+ __libc_lock_unlock (local->lock);
+ return true;
+ }
+ __libc_lock_unlock (local->lock);
+
+ /* Avoid overwriting the global configuration until we have loaded
+ everything successfully. Otherwise, if the file change
+ information changes back to what is in the global configuration,
+ the lookups would use the partially-written configuration. */
+ struct nss_database_data staging = { .initialized = true, };
+
+ bool ok = nss_database_reload (&staging, &initial);
+
+ if (ok)
+ {
+ __libc_lock_lock (local->lock);
+
+ /* See above for memory order. */
+ if (!atomic_load_acquire (&local->data.reload_disabled))
+ /* This may go back in time if another thread beats this
+ thread with the update, but in this case, a reload happens
+ on the next NSS call. */
+ local->data = staging;
+
+ *result = local->data.services[database_index];
+ __libc_lock_unlock (local->lock);
+ }
+
+ return ok;
+}
+
+bool
+__nss_database_get (enum nss_database db, nss_action_list *actions)
+{
+ struct nss_database_state *local = nss_database_state_get ();
+ return nss_database_check_reload_and_get (local, actions, db);
+}
+
+nss_action_list
+__nss_database_get_noreload (enum nss_database db)
+{
+ /* There must have been a previous __nss_database_get call. */
+ struct nss_database_state *local = atomic_load_acquire (&global_database_state);
+ assert (local != NULL);
+
+ __libc_lock_lock (local->lock);
+ nss_action_list result = local->data.services[db];
+ __libc_lock_unlock (local->lock);
+ return result;
+}
+
+void __libc_freeres_fn_section
+__nss_database_freeres (void)
+{
+ free (global_database_state);
+ global_database_state = NULL;
+}
+
+void
+__nss_database_fork_prepare_parent (struct nss_database_data *data)
+{
+ /* Do not use allocate_once to trigger loading unnecessarily. */
+ struct nss_database_state *local = atomic_load_acquire (&global_database_state);
+ if (local == NULL)
+ data->initialized = false;
+ else
+ {
+ /* Make a copy of the configuration. This approach was chosen
+ because it avoids acquiring the lock during the actual
+ fork. */
+ __libc_lock_lock (local->lock);
+ *data = local->data;
+ __libc_lock_unlock (local->lock);
+ }
+}
+
+void
+__nss_database_fork_subprocess (struct nss_database_data *data)
+{
+ struct nss_database_state *local = atomic_load_acquire (&global_database_state);
+ if (data->initialized)
+ {
+ /* Restore the state at the point of the fork. */
+ assert (local != NULL);
+ local->data = *data;
+ __libc_lock_init (local->lock);
+ }
+ else if (local != NULL)
+ /* The NSS configuration was loaded concurrently during fork. We
+ do not know its state, so we need to discard it. */
+ global_database_state = NULL;
+}
diff --git a/nss/nss_database.h b/nss/nss_database.h
new file mode 100644
index 0000000..7a91b72
--- /dev/null
+++ b/nss/nss_database.h
@@ -0,0 +1,88 @@
+/* Mapping NSS services to action lists.
+ Copyright (C) 2020 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#ifndef _NSS_DATABASE_H
+#define _NSS_DATABASE_H
+
+#include <file_change_detection.h>
+
+/* Each "line" in nsswitch.conf maps a supported database (example:
+ passwd) to one or more name service providers (example: files dns).
+ Internally, each name service provider (example: dns) is a
+ dynamically loadable module (i.e. libnss_dns.so), managed by
+ nss_module.h. The sequence of providers and rules (example: files
+ [SUCCESS=RETURN] dns) is mapped by nss_action.h to a cached entry
+ which encodes the sequence of modules and rules. Keeping track of
+ all supported databases and their corresponding actions is done
+ here.
+
+ The key entry is __nss_database_get, which provides a set of
+ actions which can be used with nss_lookup_function() and
+ nss_next(). Callers should assume that these functions are fast,
+ and should not cache the result longer than needed. */
+
+#include "nss_action.h"
+
+/* The enumeration literal in enum nss_database for the database NAME
+ (e.g., nss_database_hosts for hosts). */
+#define NSS_DATABASE_LITERAL(name) nss_database_##name
+
+enum nss_database
+{
+#define DEFINE_DATABASE(name) NSS_DATABASE_LITERAL (name),
+#include "databases.def"
+#undef DEFINE_DATABASE
+
+ /* Total number of databases. */
+ NSS_DATABASE_COUNT
+};
+
+
+/* Looks up the action list for DB and stores it in *ACTIONS. Returns
+ true on success or false on failure. Success can mean that
+ *ACTIONS is NULL. */
+bool __nss_database_get (enum nss_database db, nss_action_list *actions)
+ attribute_hidden;
+
+/* Like __nss_database_get, but does not reload /etc/nsswitch.conf
+ from disk. This assumes that there has been a previous successful
+ __nss_database_get call (which may not have returned any data). */
+nss_action_list __nss_database_get_noreload (enum nss_database db)
+ attribute_hidden;
+
+/* Called from __libc_freeres. */
+void __nss_database_freeres (void) attribute_hidden;
+
+/* Internal type. Exposed only for fork handling purposes. */
+struct nss_database_data
+{
+ struct file_change_detection nsswitch_conf;
+ nss_action_list services[NSS_DATABASE_COUNT];
+ int reload_disabled; /* Actually bool; int for atomic access. */
+ bool initialized;
+};
+
+/* Called by fork in the parent process, before forking. */
+void __nss_database_fork_prepare_parent (struct nss_database_data *data)
+ attribute_hidden;
+
+/* Called by fork in the new subprocess, after forking. */
+void __nss_database_fork_subprocess (struct nss_database_data *data)
+ attribute_hidden;
+
+#endif /* _NSS_DATABASE_H */