mirror of
git://sourceware.org/git/glibc.git
synced 2024-12-21 04:31:04 +08:00
436 lines
14 KiB
C
436 lines
14 KiB
C
/* Global list of NSS service modules.
|
|
Copyright (c) 2020-2024 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 <nsswitch.h>
|
|
#include <nscd/nscd.h>
|
|
#include <nscd/nscd_proto.h>
|
|
|
|
#include <array_length.h>
|
|
#include <assert.h>
|
|
#include <atomic.h>
|
|
#include <dlfcn.h>
|
|
#include <gnu/lib-names.h>
|
|
#include <libc-lock.h>
|
|
#include <nss_dns.h>
|
|
#include <nss_files.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <pointer_guard.h>
|
|
|
|
/* Suffix after .so of NSS service modules. This is a bit of magic,
|
|
but we assume LIBNSS_FILES_SO looks like "libnss_files.so.2" and we
|
|
want a pointer to the ".2" part. We have no API to extract this
|
|
except through the auto-generated lib-names.h and some static
|
|
pointer manipulation. The "-1" accounts for the trailing NUL
|
|
included in the sizeof. */
|
|
static const char *const __nss_shlib_revision
|
|
= LIBNSS_FILES_SO + sizeof("libnss_files.so") - 1;
|
|
|
|
/* A single-linked list used to implement a mapping from service names
|
|
to NSS modules. (Most systems only use five or so modules, so a
|
|
list is sufficient here.) Elements of this list are never freed
|
|
during normal operation. */
|
|
static struct nss_module *nss_module_list;
|
|
|
|
/* Covers the list and also loading of individual NSS service
|
|
modules. */
|
|
__libc_lock_define (static, nss_module_list_lock);
|
|
|
|
#if defined SHARED && defined USE_NSCD
|
|
/* Nonzero if this is the nscd process. */
|
|
static bool is_nscd;
|
|
/* The callback passed to the init functions when nscd is used. */
|
|
static void (*nscd_init_cb) (size_t, struct traced_file *);
|
|
#endif
|
|
|
|
/* Allocate the service NAME with length NAME_LENGTH. If the service
|
|
is already allocated in the nss_module_list cache then we return a
|
|
pointer to the struct nss_module, otherwise we try to allocate a
|
|
new struct nss_module entry and add it to the global
|
|
nss_modules_list cache. If we fail to allocate the entry we return
|
|
NULL. Failure to allocate the entry is always transient. */
|
|
struct nss_module *
|
|
__nss_module_allocate (const char *name, size_t name_length)
|
|
{
|
|
__libc_lock_lock (nss_module_list_lock);
|
|
|
|
struct nss_module *result = NULL;
|
|
for (struct nss_module *p = nss_module_list; p != NULL; p = p->next)
|
|
if (strncmp (p->name, name, name_length) == 0
|
|
&& p->name[name_length] == '\0')
|
|
{
|
|
/* Return the previously existing object. */
|
|
result = p;
|
|
break;
|
|
}
|
|
|
|
if (result == NULL)
|
|
{
|
|
/* Allocate a new list entry if the name was not found in the
|
|
list. */
|
|
result = malloc (sizeof (*result) + name_length + 1);
|
|
if (result != NULL)
|
|
{
|
|
result->state = nss_module_uninitialized;
|
|
memcpy (result->name, name, name_length);
|
|
result->name[name_length] = '\0';
|
|
result->handle = NULL;
|
|
result->next = nss_module_list;
|
|
nss_module_list = result;
|
|
}
|
|
}
|
|
|
|
__libc_lock_unlock (nss_module_list_lock);
|
|
return result;
|
|
}
|
|
|
|
/* Long enough to store the name of any function in the
|
|
nss_function_name_array list below, as getprotobynumber_r is the
|
|
longest entry in that list. */
|
|
typedef char function_name[sizeof("getprotobynumber_r")];
|
|
|
|
static const function_name nss_function_name_array[] =
|
|
{
|
|
#undef DEFINE_NSS_FUNCTION
|
|
#define DEFINE_NSS_FUNCTION(x) #x,
|
|
#include "function.def"
|
|
};
|
|
|
|
/* Loads a built-in module, binding the symbols using the supplied
|
|
callback function. Always returns true. */
|
|
static bool
|
|
module_load_builtin (struct nss_module *module,
|
|
void (*bind) (nss_module_functions_untyped))
|
|
{
|
|
/* Initialize the function pointers, following the double-checked
|
|
locking idiom. */
|
|
__libc_lock_lock (nss_module_list_lock);
|
|
switch ((enum nss_module_state) atomic_load_acquire (&module->state))
|
|
{
|
|
case nss_module_uninitialized:
|
|
case nss_module_failed:
|
|
bind (module->functions.untyped);
|
|
|
|
for (int i = 0; i < nss_module_functions_count; ++i)
|
|
PTR_MANGLE (module->functions.untyped[i]);
|
|
|
|
module->handle = NULL;
|
|
/* Synchronizes with unlocked __nss_module_load atomic_load_acquire. */
|
|
atomic_store_release (&module->state, nss_module_loaded);
|
|
break;
|
|
case nss_module_loaded:
|
|
/* Nothing to clean up. */
|
|
break;
|
|
}
|
|
__libc_lock_unlock (nss_module_list_lock);
|
|
return true;
|
|
}
|
|
|
|
/* Loads the built-in nss_files module. */
|
|
static bool
|
|
module_load_nss_files (struct nss_module *module)
|
|
{
|
|
#if defined SHARED && defined USE_NSCD
|
|
if (is_nscd)
|
|
{
|
|
void (*cb) (size_t, struct traced_file *) = nscd_init_cb;
|
|
PTR_DEMANGLE (cb);
|
|
_nss_files_init (cb);
|
|
}
|
|
#endif
|
|
return module_load_builtin (module, __nss_files_functions);
|
|
}
|
|
|
|
/* Loads the built-in nss_dns module. */
|
|
static bool
|
|
module_load_nss_dns (struct nss_module *module)
|
|
{
|
|
return module_load_builtin (module, __nss_dns_functions);
|
|
}
|
|
|
|
/* Internal implementation of __nss_module_load. */
|
|
static bool
|
|
module_load (struct nss_module *module)
|
|
{
|
|
if (strcmp (module->name, "files") == 0)
|
|
return module_load_nss_files (module);
|
|
if (strcmp (module->name, "dns") == 0)
|
|
return module_load_nss_dns (module);
|
|
|
|
void *handle;
|
|
{
|
|
char *shlib_name;
|
|
if (__asprintf (&shlib_name, "libnss_%s.so%s",
|
|
module->name, __nss_shlib_revision) < 0)
|
|
/* This is definitely a temporary failure. Do not update
|
|
module->state. This will trigger another attempt at the next
|
|
call. */
|
|
return false;
|
|
|
|
handle = __libc_dlopen (shlib_name);
|
|
free (shlib_name);
|
|
}
|
|
|
|
/* Failing to load the module can be caused by several different
|
|
scenarios. One such scenario is that the module has been removed
|
|
from the disk. In which case the in-memory version is all that
|
|
we have, and if the module->state indidates it is loaded then we
|
|
can use it. */
|
|
if (handle == NULL)
|
|
{
|
|
/* dlopen failure. We do not know if this a temporary or
|
|
permanent error. See bug 22041. Update the state using the
|
|
double-checked locking idiom. */
|
|
|
|
__libc_lock_lock (nss_module_list_lock);
|
|
bool result = result;
|
|
switch ((enum nss_module_state) atomic_load_acquire (&module->state))
|
|
{
|
|
case nss_module_uninitialized:
|
|
atomic_store_release (&module->state, nss_module_failed);
|
|
result = false;
|
|
break;
|
|
case nss_module_loaded:
|
|
result = true;
|
|
break;
|
|
case nss_module_failed:
|
|
result = false;
|
|
break;
|
|
}
|
|
__libc_lock_unlock (nss_module_list_lock);
|
|
return result;
|
|
}
|
|
|
|
nss_module_functions_untyped pointers;
|
|
|
|
/* Look up and store locally all the function pointers we may need
|
|
later. Doing this now means the data will not change in the
|
|
future. */
|
|
for (size_t idx = 0; idx < array_length (nss_function_name_array); ++idx)
|
|
{
|
|
char *function_name;
|
|
if (__asprintf (&function_name, "_nss_%s_%s",
|
|
module->name, nss_function_name_array[idx]) < 0)
|
|
{
|
|
/* Definitely a temporary error. */
|
|
__libc_dlclose (handle);
|
|
return false;
|
|
}
|
|
pointers[idx] = __libc_dlsym (handle, function_name);
|
|
free (function_name);
|
|
PTR_MANGLE (pointers[idx]);
|
|
}
|
|
|
|
# if defined SHARED && defined USE_NSCD
|
|
if (is_nscd)
|
|
{
|
|
/* Call the init function when nscd is used. */
|
|
size_t initlen = (5 + strlen (module->name)
|
|
+ strlen ("_init") + 1);
|
|
char init_name[initlen];
|
|
|
|
/* Construct the init function name. */
|
|
__stpcpy (__stpcpy (__stpcpy (init_name,
|
|
"_nss_"),
|
|
module->name),
|
|
"_init");
|
|
|
|
/* Find the optional init function. */
|
|
void (*ifct) (void (*) (size_t, struct traced_file *))
|
|
= __libc_dlsym (handle, init_name);
|
|
if (ifct != NULL)
|
|
{
|
|
void (*cb) (size_t, struct traced_file *) = nscd_init_cb;
|
|
PTR_DEMANGLE (cb);
|
|
ifct (cb);
|
|
}
|
|
}
|
|
# endif
|
|
|
|
/* Install the function pointers, following the double-checked
|
|
locking idiom. Delay this after all processing, in case loading
|
|
the module triggers unwinding. */
|
|
__libc_lock_lock (nss_module_list_lock);
|
|
switch ((enum nss_module_state) atomic_load_acquire (&module->state))
|
|
{
|
|
case nss_module_uninitialized:
|
|
case nss_module_failed:
|
|
memcpy (module->functions.untyped, pointers,
|
|
sizeof (module->functions.untyped));
|
|
module->handle = handle;
|
|
/* Synchronizes with unlocked __nss_module_load atomic_load_acquire. */
|
|
atomic_store_release (&module->state, nss_module_loaded);
|
|
break;
|
|
case nss_module_loaded:
|
|
/* If the module was already loaded, close our own handle. This
|
|
does not actually unload the modules, only the reference
|
|
counter is decremented for the loaded module. */
|
|
__libc_dlclose (handle);
|
|
break;
|
|
}
|
|
__libc_lock_unlock (nss_module_list_lock);
|
|
return true;
|
|
}
|
|
|
|
/* Force the module identified by MODULE to be loaded. We return
|
|
false if the module could not be loaded, true otherwise. Loading
|
|
the module requires looking up all the possible interface APIs and
|
|
caching the results. */
|
|
bool
|
|
__nss_module_load (struct nss_module *module)
|
|
{
|
|
switch ((enum nss_module_state) atomic_load_acquire (&module->state))
|
|
{
|
|
case nss_module_uninitialized:
|
|
return module_load (module);
|
|
case nss_module_loaded:
|
|
/* Loading has already succeeded. */
|
|
return true;
|
|
case nss_module_failed:
|
|
/* Loading previously failed. */
|
|
return false;
|
|
}
|
|
__builtin_unreachable ();
|
|
}
|
|
|
|
static int
|
|
name_search (const void *left, const void *right)
|
|
{
|
|
return strcmp (left, right);
|
|
}
|
|
|
|
/* Load module MODULE (if it isn't already) and return a pointer to
|
|
the module's implementation of NAME, otherwise return NULL on
|
|
failure or error. */
|
|
void *
|
|
__nss_module_get_function (struct nss_module *module, const char *name)
|
|
{
|
|
/* A successful dlopen might clobber errno. */
|
|
int saved_errno = errno;
|
|
|
|
if (!__nss_module_load (module))
|
|
{
|
|
/* Reporting module load failure is currently inaccurate. See
|
|
bug 22041. Not changing errno is the conservative choice. */
|
|
__set_errno (saved_errno);
|
|
return NULL;
|
|
}
|
|
|
|
__set_errno (saved_errno);
|
|
|
|
function_name *name_entry = bsearch (name, nss_function_name_array,
|
|
array_length (nss_function_name_array),
|
|
sizeof (function_name), name_search);
|
|
assert (name_entry != NULL);
|
|
size_t idx = name_entry - nss_function_name_array;
|
|
void *fptr = module->functions.untyped[idx];
|
|
PTR_DEMANGLE (fptr);
|
|
return fptr;
|
|
}
|
|
|
|
#if defined SHARED && defined USE_NSCD
|
|
/* Load all libraries for the service. */
|
|
static void
|
|
nss_load_all_libraries (enum nss_database service)
|
|
{
|
|
nss_action_list ni = NULL;
|
|
|
|
if (__nss_database_get (service, &ni))
|
|
while (ni->module != NULL)
|
|
{
|
|
__nss_module_load (ni->module);
|
|
++ni;
|
|
}
|
|
}
|
|
|
|
define_traced_file (pwd, _PATH_NSSWITCH_CONF);
|
|
define_traced_file (grp, _PATH_NSSWITCH_CONF);
|
|
define_traced_file (hst, _PATH_NSSWITCH_CONF);
|
|
define_traced_file (serv, _PATH_NSSWITCH_CONF);
|
|
define_traced_file (netgr, _PATH_NSSWITCH_CONF);
|
|
|
|
/* Called by nscd and nscd alone. */
|
|
void
|
|
__nss_disable_nscd (void (*cb) (size_t, struct traced_file *))
|
|
{
|
|
void (*cb1) (size_t, struct traced_file *);
|
|
cb1 = cb;
|
|
PTR_MANGLE (cb);
|
|
nscd_init_cb = cb;
|
|
is_nscd = true;
|
|
|
|
/* Find all the relevant modules so that the init functions are called. */
|
|
nss_load_all_libraries (nss_database_passwd);
|
|
nss_load_all_libraries (nss_database_group);
|
|
nss_load_all_libraries (nss_database_hosts);
|
|
nss_load_all_libraries (nss_database_services);
|
|
|
|
/* Make sure NSCD purges its cache if nsswitch.conf changes. */
|
|
init_traced_file (&pwd_traced_file.file, _PATH_NSSWITCH_CONF, 0);
|
|
cb1 (pwddb, &pwd_traced_file.file);
|
|
init_traced_file (&grp_traced_file.file, _PATH_NSSWITCH_CONF, 0);
|
|
cb1 (grpdb, &grp_traced_file.file);
|
|
init_traced_file (&hst_traced_file.file, _PATH_NSSWITCH_CONF, 0);
|
|
cb1 (hstdb, &hst_traced_file.file);
|
|
init_traced_file (&serv_traced_file.file, _PATH_NSSWITCH_CONF, 0);
|
|
cb1 (servdb, &serv_traced_file.file);
|
|
init_traced_file (&netgr_traced_file.file, _PATH_NSSWITCH_CONF, 0);
|
|
cb1 (netgrdb, &netgr_traced_file.file);
|
|
|
|
/* Disable all uses of NSCD. */
|
|
__nss_not_use_nscd_passwd = -1;
|
|
__nss_not_use_nscd_group = -1;
|
|
__nss_not_use_nscd_hosts = -1;
|
|
__nss_not_use_nscd_services = -1;
|
|
__nss_not_use_nscd_netgroup = -1;
|
|
}
|
|
#endif
|
|
|
|
/* Block attempts to dlopen any module we haven't already opened. */
|
|
void
|
|
__nss_module_disable_loading (void)
|
|
{
|
|
__libc_lock_lock (nss_module_list_lock);
|
|
|
|
for (struct nss_module *p = nss_module_list; p != NULL; p = p->next)
|
|
if (p->state == nss_module_uninitialized)
|
|
p->state = nss_module_failed;
|
|
|
|
__libc_lock_unlock (nss_module_list_lock);
|
|
}
|
|
|
|
void
|
|
__nss_module_freeres (void)
|
|
{
|
|
struct nss_module *current = nss_module_list;
|
|
while (current != NULL)
|
|
{
|
|
/* Ignore built-in modules (which have a NULL handle). */
|
|
if (current->state == nss_module_loaded
|
|
&& current->handle != NULL)
|
|
__libc_dlclose (current->handle);
|
|
|
|
struct nss_module *next = current->next;
|
|
free (current);
|
|
current = next;
|
|
}
|
|
nss_module_list = NULL;
|
|
}
|