1 | /* Global list of NSS service modules. |
2 | Copyright (c) 2020-2021 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <nsswitch.h> |
20 | #include <nscd/nscd.h> |
21 | #include <nscd/nscd_proto.h> |
22 | |
23 | #include <array_length.h> |
24 | #include <assert.h> |
25 | #include <atomic.h> |
26 | #include <dlfcn.h> |
27 | #include <gnu/lib-names.h> |
28 | #include <libc-lock.h> |
29 | #include <stddef.h> |
30 | #include <stdio.h> |
31 | #include <stdlib.h> |
32 | #include <string.h> |
33 | |
34 | #ifdef LINK_OBSOLETE_NSL |
35 | # define DEFAULT_CONFIG "compat [NOTFOUND=return] files" |
36 | # define DEFAULT_DEFCONFIG "nis [NOTFOUND=return] files" |
37 | #else |
38 | # define DEFAULT_CONFIG "files" |
39 | # define DEFAULT_DEFCONFIG "files" |
40 | #endif |
41 | |
42 | /* Suffix after .so of NSS service modules. This is a bit of magic, |
43 | but we assume LIBNSS_FILES_SO looks like "libnss_files.so.2" and we |
44 | want a pointer to the ".2" part. We have no API to extract this |
45 | except through the auto-generated lib-names.h and some static |
46 | pointer manipulation. The "-1" accounts for the trailing NUL |
47 | included in the sizeof. */ |
48 | static const char *const __nss_shlib_revision |
49 | = LIBNSS_FILES_SO + sizeof("libnss_files.so" ) - 1; |
50 | |
51 | /* A single-linked list used to implement a mapping from service names |
52 | to NSS modules. (Most systems only use five or so modules, so a |
53 | list is sufficient here.) Elements of this list are never freed |
54 | during normal operation. */ |
55 | static struct nss_module *nss_module_list; |
56 | |
57 | /* Covers the list and also loading of individual NSS service |
58 | modules. */ |
59 | __libc_lock_define (static, nss_module_list_lock); |
60 | |
61 | #if defined USE_NSCD && (!defined DO_STATIC_NSS || defined SHARED) |
62 | /* Nonzero if this is the nscd process. */ |
63 | static bool is_nscd; |
64 | /* The callback passed to the init functions when nscd is used. */ |
65 | static void (*nscd_init_cb) (size_t, struct traced_file *); |
66 | #endif |
67 | |
68 | /* Allocate the service NAME with length NAME_LENGTH. If the service |
69 | is already allocated in the nss_module_list cache then we return a |
70 | pointer to the struct nss_module, otherwise we try to allocate a |
71 | new struct nss_module entry and add it to the global |
72 | nss_modules_list cache. If we fail to allocate the entry we return |
73 | NULL. Failure to allocate the entry is always transient. */ |
74 | struct nss_module * |
75 | __nss_module_allocate (const char *name, size_t name_length) |
76 | { |
77 | __libc_lock_lock (nss_module_list_lock); |
78 | |
79 | struct nss_module *result = NULL; |
80 | for (struct nss_module *p = nss_module_list; p != NULL; p = p->next) |
81 | if (strncmp (p->name, name, name_length) == 0 |
82 | && p->name[name_length] == '\0') |
83 | { |
84 | /* Return the previously existing object. */ |
85 | result = p; |
86 | break; |
87 | } |
88 | |
89 | if (result == NULL) |
90 | { |
91 | /* Allocate a new list entry if the name was not found in the |
92 | list. */ |
93 | result = malloc (sizeof (*result) + name_length + 1); |
94 | if (result != NULL) |
95 | { |
96 | result->state = nss_module_uninitialized; |
97 | memcpy (result->name, name, name_length); |
98 | result->name[name_length] = '\0'; |
99 | result->handle = NULL; |
100 | result->next = nss_module_list; |
101 | nss_module_list = result; |
102 | } |
103 | } |
104 | |
105 | __libc_lock_unlock (nss_module_list_lock); |
106 | return result; |
107 | } |
108 | |
109 | /* Long enough to store the name of any function in the |
110 | nss_function_name_array list below, as getprotobynumber_r is the |
111 | longest entry in that list. */ |
112 | typedef char function_name[sizeof("getprotobynumber_r" )]; |
113 | |
114 | static const function_name nss_function_name_array[] = |
115 | { |
116 | #undef DEFINE_NSS_FUNCTION |
117 | #define DEFINE_NSS_FUNCTION(x) #x, |
118 | #include "function.def" |
119 | }; |
120 | |
121 | /* Internal implementation of __nss_module_load. */ |
122 | static bool |
123 | module_load (struct nss_module *module) |
124 | { |
125 | void *handle; |
126 | { |
127 | char *shlib_name; |
128 | if (__asprintf (&shlib_name, "libnss_%s.so%s" , |
129 | module->name, __nss_shlib_revision) < 0) |
130 | /* This is definitely a temporary failure. Do not update |
131 | module->state. This will trigger another attempt at the next |
132 | call. */ |
133 | return false; |
134 | |
135 | handle = __libc_dlopen (shlib_name); |
136 | free (shlib_name); |
137 | } |
138 | |
139 | /* Failing to load the module can be caused by several different |
140 | scenarios. One such scenario is that the module has been removed |
141 | from the disk. In which case the in-memory version is all that |
142 | we have, and if the module->state indidates it is loaded then we |
143 | can use it. */ |
144 | if (handle == NULL) |
145 | { |
146 | /* dlopen failure. We do not know if this a temporary or |
147 | permanent error. See bug 22041. Update the state using the |
148 | double-checked locking idiom. */ |
149 | |
150 | __libc_lock_lock (nss_module_list_lock); |
151 | bool result = result; |
152 | switch ((enum nss_module_state) atomic_load_acquire (&module->state)) |
153 | { |
154 | case nss_module_uninitialized: |
155 | atomic_store_release (&module->state, nss_module_failed); |
156 | result = false; |
157 | break; |
158 | case nss_module_loaded: |
159 | result = true; |
160 | break; |
161 | case nss_module_failed: |
162 | result = false; |
163 | break; |
164 | } |
165 | __libc_lock_unlock (nss_module_list_lock); |
166 | return result; |
167 | } |
168 | |
169 | nss_module_functions_untyped pointers; |
170 | |
171 | /* Look up and store locally all the function pointers we may need |
172 | later. Doing this now means the data will not change in the |
173 | future. */ |
174 | for (size_t idx = 0; idx < array_length (nss_function_name_array); ++idx) |
175 | { |
176 | char *function_name; |
177 | if (__asprintf (&function_name, "_nss_%s_%s" , |
178 | module->name, nss_function_name_array[idx]) < 0) |
179 | { |
180 | /* Definitely a temporary error. */ |
181 | __libc_dlclose (handle); |
182 | return false; |
183 | } |
184 | pointers[idx] = __libc_dlsym (handle, function_name); |
185 | free (function_name); |
186 | #ifdef PTR_MANGLE |
187 | PTR_MANGLE (pointers[idx]); |
188 | #endif |
189 | } |
190 | |
191 | # ifdef USE_NSCD |
192 | if (is_nscd) |
193 | { |
194 | /* Call the init function when nscd is used. */ |
195 | size_t initlen = (5 + strlen (module->name) |
196 | + strlen ("_init" ) + 1); |
197 | char init_name[initlen]; |
198 | |
199 | /* Construct the init function name. */ |
200 | __stpcpy (__stpcpy (__stpcpy (init_name, |
201 | "_nss_" ), |
202 | module->name), |
203 | "_init" ); |
204 | |
205 | /* Find the optional init function. */ |
206 | void (*ifct) (void (*) (size_t, struct traced_file *)) |
207 | = __libc_dlsym (handle, init_name); |
208 | if (ifct != NULL) |
209 | { |
210 | void (*cb) (size_t, struct traced_file *) = nscd_init_cb; |
211 | # ifdef PTR_DEMANGLE |
212 | PTR_DEMANGLE (cb); |
213 | # endif |
214 | ifct (cb); |
215 | } |
216 | } |
217 | # endif |
218 | |
219 | /* Install the function pointers, following the double-checked |
220 | locking idiom. Delay this after all processing, in case loading |
221 | the module triggers unwinding. */ |
222 | __libc_lock_lock (nss_module_list_lock); |
223 | switch ((enum nss_module_state) atomic_load_acquire (&module->state)) |
224 | { |
225 | case nss_module_uninitialized: |
226 | case nss_module_failed: |
227 | memcpy (module->functions.untyped, pointers, |
228 | sizeof (module->functions.untyped)); |
229 | module->handle = handle; |
230 | /* Synchronizes with unlocked __nss_module_load atomic_load_acquire. */ |
231 | atomic_store_release (&module->state, nss_module_loaded); |
232 | break; |
233 | case nss_module_loaded: |
234 | /* If the module was already loaded, close our own handle. This |
235 | does not actually unload the modules, only the reference |
236 | counter is decremented for the loaded module. */ |
237 | __libc_dlclose (handle); |
238 | break; |
239 | } |
240 | __libc_lock_unlock (nss_module_list_lock); |
241 | return true; |
242 | } |
243 | |
244 | /* Force the module identified by MODULE to be loaded. We return |
245 | false if the module could not be loaded, true otherwise. Loading |
246 | the module requires looking up all the possible interface APIs and |
247 | caching the results. */ |
248 | bool |
249 | __nss_module_load (struct nss_module *module) |
250 | { |
251 | switch ((enum nss_module_state) atomic_load_acquire (&module->state)) |
252 | { |
253 | case nss_module_uninitialized: |
254 | return module_load (module); |
255 | case nss_module_loaded: |
256 | /* Loading has already succeeded. */ |
257 | return true; |
258 | case nss_module_failed: |
259 | /* Loading previously failed. */ |
260 | return false; |
261 | } |
262 | __builtin_unreachable (); |
263 | } |
264 | |
265 | static int |
266 | name_search (const void *left, const void *right) |
267 | { |
268 | return strcmp (left, right); |
269 | } |
270 | |
271 | /* Load module MODULE (if it isn't already) and return a pointer to |
272 | the module's implementation of NAME, otherwise return NULL on |
273 | failure or error. */ |
274 | void * |
275 | __nss_module_get_function (struct nss_module *module, const char *name) |
276 | { |
277 | if (!__nss_module_load (module)) |
278 | return NULL; |
279 | |
280 | function_name *name_entry = bsearch (name, nss_function_name_array, |
281 | array_length (nss_function_name_array), |
282 | sizeof (function_name), name_search); |
283 | assert (name_entry != NULL); |
284 | size_t idx = name_entry - nss_function_name_array; |
285 | void *fptr = module->functions.untyped[idx]; |
286 | #ifdef PTR_DEMANGLE |
287 | PTR_DEMANGLE (fptr); |
288 | #endif |
289 | return fptr; |
290 | } |
291 | |
292 | #if defined SHARED && defined USE_NSCD |
293 | /* Load all libraries for the service. */ |
294 | static void |
295 | nss_load_all_libraries (const char *service, const char *def) |
296 | { |
297 | nss_action_list ni = NULL; |
298 | |
299 | if (__nss_database_lookup2 (service, NULL, def, &ni) == 0) |
300 | while (ni->module != NULL) |
301 | { |
302 | __nss_module_load (ni->module); |
303 | ++ni; |
304 | } |
305 | } |
306 | |
307 | define_traced_file (pwd, _PATH_NSSWITCH_CONF); |
308 | define_traced_file (grp, _PATH_NSSWITCH_CONF); |
309 | define_traced_file (hst, _PATH_NSSWITCH_CONF); |
310 | define_traced_file (serv, _PATH_NSSWITCH_CONF); |
311 | define_traced_file (netgr, _PATH_NSSWITCH_CONF); |
312 | |
313 | /* Called by nscd and nscd alone. */ |
314 | void |
315 | __nss_disable_nscd (void (*cb) (size_t, struct traced_file *)) |
316 | { |
317 | void (*cb1) (size_t, struct traced_file *); |
318 | cb1 = cb; |
319 | # ifdef PTR_MANGLE |
320 | PTR_MANGLE (cb); |
321 | # endif |
322 | nscd_init_cb = cb; |
323 | is_nscd = true; |
324 | |
325 | /* Find all the relevant modules so that the init functions are called. */ |
326 | nss_load_all_libraries ("passwd" , DEFAULT_CONFIG); |
327 | nss_load_all_libraries ("group" , DEFAULT_CONFIG); |
328 | nss_load_all_libraries ("hosts" , "dns [!UNAVAIL=return] files" ); |
329 | nss_load_all_libraries ("services" , NULL); |
330 | |
331 | /* Make sure NSCD purges its cache if nsswitch.conf changes. */ |
332 | init_traced_file (&pwd_traced_file.file, _PATH_NSSWITCH_CONF, 0); |
333 | cb1 (pwddb, &pwd_traced_file.file); |
334 | init_traced_file (&grp_traced_file.file, _PATH_NSSWITCH_CONF, 0); |
335 | cb1 (grpdb, &grp_traced_file.file); |
336 | init_traced_file (&hst_traced_file.file, _PATH_NSSWITCH_CONF, 0); |
337 | cb1 (hstdb, &hst_traced_file.file); |
338 | init_traced_file (&serv_traced_file.file, _PATH_NSSWITCH_CONF, 0); |
339 | cb1 (servdb, &serv_traced_file.file); |
340 | init_traced_file (&netgr_traced_file.file, _PATH_NSSWITCH_CONF, 0); |
341 | cb1 (netgrdb, &netgr_traced_file.file); |
342 | |
343 | /* Disable all uses of NSCD. */ |
344 | __nss_not_use_nscd_passwd = -1; |
345 | __nss_not_use_nscd_group = -1; |
346 | __nss_not_use_nscd_hosts = -1; |
347 | __nss_not_use_nscd_services = -1; |
348 | __nss_not_use_nscd_netgroup = -1; |
349 | } |
350 | #endif |
351 | |
352 | /* Block attempts to dlopen any module we haven't already opened. */ |
353 | void |
354 | __nss_module_disable_loading (void) |
355 | { |
356 | __libc_lock_lock (nss_module_list_lock); |
357 | |
358 | for (struct nss_module *p = nss_module_list; p != NULL; p = p->next) |
359 | if (p->state == nss_module_uninitialized) |
360 | p->state = nss_module_failed; |
361 | |
362 | __libc_lock_unlock (nss_module_list_lock); |
363 | } |
364 | |
365 | void __libc_freeres_fn_section |
366 | __nss_module_freeres (void) |
367 | { |
368 | struct nss_module *current = nss_module_list; |
369 | while (current != NULL) |
370 | { |
371 | if (current->state == nss_module_loaded) |
372 | __libc_dlclose (current->handle); |
373 | |
374 | struct nss_module *next = current->next; |
375 | free (current); |
376 | current = next; |
377 | } |
378 | nss_module_list = NULL; |
379 | } |
380 | |