]>
Commit | Line | Data |
---|---|---|
fa78feca | 1 | /* Mapping NSS services to action lists. |
6d7e8eda | 2 | Copyright (C) 2020-2023 Free Software Foundation, Inc. |
fa78feca FW |
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 "nss_database.h" | |
20 | ||
21 | #include <allocate_once.h> | |
22 | #include <array_length.h> | |
23 | #include <assert.h> | |
24 | #include <atomic.h> | |
25 | #include <ctype.h> | |
26 | #include <file_change_detection.h> | |
27 | #include <libc-lock.h> | |
28 | #include <netdb.h> | |
29 | #include <stdio_ext.h> | |
30 | #include <string.h> | |
31 | ||
32 | struct nss_database_state | |
33 | { | |
34 | struct nss_database_data data; | |
35 | __libc_lock_define (, lock); | |
429029a7 DD |
36 | /* If "/" changes, we switched into a container and do NOT want to |
37 | reload anything. This data must be persistent across | |
38 | reloads. */ | |
39 | ino64_t root_ino; | |
40 | dev_t root_dev; | |
fa78feca FW |
41 | }; |
42 | ||
43 | ||
44 | /* Global NSS database state. Underlying type is "struct | |
45 | nss_database_state *" but the allocate_once API requires | |
46 | "void *". */ | |
47 | static void *global_database_state; | |
48 | ||
49 | /* Allocate and return pointer to nss_database_state object or | |
50 | on failure return NULL. */ | |
51 | static void * | |
52 | global_state_allocate (void *closure) | |
53 | { | |
54 | struct nss_database_state *result = malloc (sizeof (*result)); | |
55 | if (result != NULL) | |
56 | { | |
57 | result->data.nsswitch_conf.size = -1; /* Force reload. */ | |
58 | memset (result->data.services, 0, sizeof (result->data.services)); | |
59 | result->data.initialized = true; | |
60 | result->data.reload_disabled = false; | |
61 | __libc_lock_init (result->lock); | |
429029a7 DD |
62 | result->root_ino = 0; |
63 | result->root_dev = 0; | |
fa78feca FW |
64 | } |
65 | return result; | |
66 | } | |
67 | ||
68 | /* Return pointer to global NSS database state, allocating as | |
69 | required, or returning NULL on failure. */ | |
70 | static struct nss_database_state * | |
71 | nss_database_state_get (void) | |
72 | { | |
73 | return allocate_once (&global_database_state, global_state_allocate, | |
74 | NULL, NULL); | |
75 | } | |
76 | ||
77 | /* Database default selections. nis/compat mappings get turned into | |
78 | "files" for !LINK_OBSOLETE_NSL configurations. */ | |
79 | enum nss_database_default | |
80 | { | |
81 | nss_database_default_defconfig = 0, /* "nis [NOTFOUND=return] files". */ | |
82 | nss_database_default_compat, /* "compat [NOTFOUND=return] files". */ | |
b99b0f93 | 83 | nss_database_default_dns, /* "files dns". */ |
fa78feca FW |
84 | nss_database_default_files, /* "files". */ |
85 | nss_database_default_nis, /* "nis". */ | |
86 | nss_database_default_nis_nisplus, /* "nis nisplus". */ | |
87 | nss_database_default_none, /* Empty list. */ | |
88 | ||
89 | NSS_DATABASE_DEFAULT_COUNT /* Number of defaults. */ | |
90 | }; | |
91 | ||
92 | /* Databases not listed default to nss_database_default_defconfig. */ | |
93 | static const char per_database_defaults[NSS_DATABASE_COUNT] = | |
94 | { | |
95 | [nss_database_group] = nss_database_default_compat, | |
9b456c5d | 96 | [nss_database_group_compat] = nss_database_default_nis, |
fa78feca FW |
97 | [nss_database_gshadow] = nss_database_default_files, |
98 | [nss_database_hosts] = nss_database_default_dns, | |
99 | [nss_database_initgroups] = nss_database_default_none, | |
100 | [nss_database_networks] = nss_database_default_dns, | |
101 | [nss_database_passwd] = nss_database_default_compat, | |
9b456c5d | 102 | [nss_database_passwd_compat] = nss_database_default_nis, |
fa78feca FW |
103 | [nss_database_publickey] = nss_database_default_nis_nisplus, |
104 | [nss_database_shadow] = nss_database_default_compat, | |
9b456c5d | 105 | [nss_database_shadow_compat] = nss_database_default_nis, |
fa78feca FW |
106 | }; |
107 | ||
108 | struct nss_database_default_cache | |
109 | { | |
110 | nss_action_list caches[NSS_DATABASE_DEFAULT_COUNT]; | |
111 | }; | |
112 | ||
113 | static bool | |
114 | nss_database_select_default (struct nss_database_default_cache *cache, | |
115 | enum nss_database db, nss_action_list *result) | |
116 | { | |
117 | enum nss_database_default def = per_database_defaults[db]; | |
118 | *result = cache->caches[def]; | |
119 | if (*result != NULL) | |
120 | return true; | |
121 | ||
122 | /* Determine the default line string. */ | |
123 | const char *line; | |
124 | switch (def) | |
125 | { | |
126 | #ifdef LINK_OBSOLETE_NSL | |
127 | case nss_database_default_defconfig: | |
128 | line = "nis [NOTFOUND=return] files"; | |
129 | break; | |
130 | case nss_database_default_compat: | |
131 | line = "compat [NOTFOUND=return] files"; | |
132 | break; | |
133 | #endif | |
134 | ||
135 | case nss_database_default_dns: | |
b99b0f93 | 136 | line = "files dns"; |
fa78feca FW |
137 | break; |
138 | ||
139 | case nss_database_default_files: | |
140 | #ifndef LINK_OBSOLETE_NSL | |
141 | case nss_database_default_defconfig: | |
142 | case nss_database_default_compat: | |
143 | #endif | |
144 | line = "files"; | |
145 | break; | |
146 | ||
147 | case nss_database_default_nis: | |
148 | line = "nis"; | |
149 | break; | |
150 | ||
151 | case nss_database_default_nis_nisplus: | |
152 | line = "nis nisplus"; | |
153 | break; | |
154 | ||
155 | case nss_database_default_none: | |
156 | /* Very special case: Leave *result as NULL. */ | |
157 | return true; | |
158 | ||
159 | case NSS_DATABASE_DEFAULT_COUNT: | |
160 | __builtin_unreachable (); | |
161 | } | |
162 | if (def < 0 || def >= NSS_DATABASE_DEFAULT_COUNT) | |
163 | /* Tell GCC that line is initialized. */ | |
164 | __builtin_unreachable (); | |
165 | ||
166 | *result = __nss_action_parse (line); | |
167 | if (*result == NULL) | |
168 | { | |
169 | assert (errno == ENOMEM); | |
170 | return false; | |
171 | } | |
9b456c5d | 172 | return true; |
fa78feca FW |
173 | } |
174 | ||
175 | /* database_name must be large enough for each individual name plus a | |
176 | null terminator. */ | |
9b456c5d | 177 | typedef char database_name[14]; |
fa78feca FW |
178 | #define DEFINE_DATABASE(name) \ |
179 | _Static_assert (sizeof (#name) <= sizeof (database_name), #name); | |
180 | #include "databases.def" | |
181 | #undef DEFINE_DATABASE | |
182 | ||
183 | static const database_name nss_database_name_array[] = | |
184 | { | |
185 | #define DEFINE_DATABASE(name) #name, | |
186 | #include "databases.def" | |
187 | #undef DEFINE_DATABASE | |
188 | }; | |
189 | ||
190 | static int | |
191 | name_search (const void *left, const void *right) | |
192 | { | |
193 | return strcmp (left, right); | |
194 | } | |
195 | ||
196 | static int | |
197 | name_to_database_index (const char *name) | |
198 | { | |
199 | database_name *name_entry = bsearch (name, nss_database_name_array, | |
200 | array_length (nss_database_name_array), | |
201 | sizeof (database_name), name_search); | |
202 | if (name_entry == NULL) | |
203 | return -1; | |
204 | return name_entry - nss_database_name_array; | |
205 | } | |
206 | ||
207 | static bool | |
208 | process_line (struct nss_database_data *data, char *line) | |
209 | { | |
210 | /* Ignore leading white spaces. ATTENTION: this is different from | |
211 | what is implemented in Solaris. The Solaris man page says a line | |
212 | beginning with a white space character is ignored. We regard | |
213 | this as just another misfeature in Solaris. */ | |
214 | while (isspace (line[0])) | |
215 | ++line; | |
216 | ||
217 | /* Recognize `<database> ":"'. */ | |
218 | char *name = line; | |
219 | while (line[0] != '\0' && !isspace (line[0]) && line[0] != ':') | |
220 | ++line; | |
221 | if (line[0] == '\0' || name == line) | |
222 | /* Syntax error. Skip this line. */ | |
223 | return true; | |
d2e929a9 DD |
224 | while (line[0] != '\0' && (isspace (line[0]) || line[0] == ':')) |
225 | *line++ = '\0'; | |
fa78feca FW |
226 | |
227 | int db = name_to_database_index (name); | |
228 | if (db < 0) | |
229 | /* Not our database e.g. sudoers, automount, etc. */ | |
230 | return true; | |
231 | ||
232 | nss_action_list result = __nss_action_parse (line); | |
233 | if (result == NULL) | |
234 | return false; | |
235 | data->services[db] = result; | |
236 | return true; | |
237 | } | |
238 | ||
f8847d83 DD |
239 | int |
240 | __nss_configure_lookup (const char *dbname, const char *service_line) | |
241 | { | |
242 | int db; | |
243 | nss_action_list result; | |
244 | struct nss_database_state *local; | |
245 | ||
246 | /* Convert named database to index. */ | |
247 | db = name_to_database_index (dbname); | |
248 | if (db < 0) | |
249 | /* Not our database (e.g., sudoers). */ | |
250 | return -1; | |
251 | ||
252 | /* Force any load/cache/read whatever to happen, so we can override | |
253 | it. */ | |
254 | __nss_database_get (db, &result); | |
255 | ||
256 | local = nss_database_state_get (); | |
257 | ||
258 | result = __nss_action_parse (service_line); | |
259 | if (result == NULL) | |
260 | return -1; | |
261 | ||
262 | atomic_store_release (&local->data.reload_disabled, 1); | |
263 | local->data.services[db] = result; | |
264 | ||
265 | #ifdef USE_NSCD | |
266 | __nss_database_custom[db] = true; | |
267 | #endif | |
268 | ||
269 | return 0; | |
270 | } | |
271 | ||
fa78feca FW |
272 | /* Iterate over the lines in FP, parse them, and store them in DATA. |
273 | Return false on memory allocation failure, true on success. */ | |
274 | static bool | |
275 | nss_database_reload_1 (struct nss_database_data *data, FILE *fp) | |
276 | { | |
277 | char *line = NULL; | |
278 | size_t line_allocated = 0; | |
279 | bool result = false; | |
280 | ||
281 | while (true) | |
282 | { | |
283 | ssize_t ret = __getline (&line, &line_allocated, fp); | |
6f19927b | 284 | if (__ferror_unlocked (fp)) |
fa78feca | 285 | break; |
6f19927b | 286 | if (__feof_unlocked (fp)) |
fa78feca FW |
287 | { |
288 | result = true; | |
289 | break; | |
290 | } | |
291 | assert (ret > 0); | |
292 | (void) ret; /* For NDEBUG builds. */ | |
293 | ||
294 | if (!process_line (data, line)) | |
295 | break; | |
296 | } | |
297 | ||
298 | free (line); | |
299 | return result; | |
300 | } | |
301 | ||
302 | static bool | |
303 | nss_database_reload (struct nss_database_data *staging, | |
304 | struct file_change_detection *initial) | |
305 | { | |
306 | FILE *fp = fopen (_PATH_NSSWITCH_CONF, "rce"); | |
307 | if (fp == NULL) | |
308 | switch (errno) | |
309 | { | |
310 | case EACCES: | |
311 | case EISDIR: | |
312 | case ELOOP: | |
313 | case ENOENT: | |
314 | case ENOTDIR: | |
315 | case EPERM: | |
316 | /* Ignore these errors. They are persistent errors caused | |
317 | by file system contents. */ | |
318 | break; | |
319 | default: | |
320 | /* Other errors refer to resource allocation problems and | |
321 | need to be handled by the application. */ | |
322 | return false; | |
323 | } | |
324 | else | |
325 | /* No other threads have access to fp. */ | |
326 | __fsetlocking (fp, FSETLOCKING_BYCALLER); | |
327 | ||
9b456c5d DD |
328 | /* We start with all of *staging pointing to NULL. */ |
329 | ||
fa78feca FW |
330 | bool ok = true; |
331 | if (fp != NULL) | |
332 | ok = nss_database_reload_1 (staging, fp); | |
333 | ||
7f0d9e61 | 334 | /* Now we have non-NULL entries where the user explicitly listed the |
9b456c5d DD |
335 | service in nsswitch.conf. */ |
336 | ||
fa78feca FW |
337 | /* Apply defaults. */ |
338 | if (ok) | |
339 | { | |
340 | struct nss_database_default_cache cache = { }; | |
9b456c5d DD |
341 | |
342 | /* These three default to other services if the user listed the | |
343 | other service. */ | |
344 | ||
345 | /* "shadow_compat" defaults to "passwd_compat" if only the | |
346 | latter is given. */ | |
347 | if (staging->services[nss_database_shadow_compat] == NULL) | |
348 | staging->services[nss_database_shadow_compat] = | |
349 | staging->services[nss_database_passwd_compat]; | |
350 | ||
351 | /* "shadow" defaults to "passwd" if only the latter is | |
352 | given. */ | |
353 | if (staging->services[nss_database_shadow] == NULL) | |
354 | staging->services[nss_database_shadow] = | |
355 | staging->services[nss_database_passwd]; | |
356 | ||
357 | /* "gshadow" defaults to "group" if only the latter is | |
358 | given. */ | |
359 | if (staging->services[nss_database_gshadow] == NULL) | |
360 | staging->services[nss_database_gshadow] = | |
361 | staging->services[nss_database_group]; | |
362 | ||
363 | /* For anything still unspecified, load the default configs. */ | |
364 | ||
fa78feca FW |
365 | for (int i = 0; i < NSS_DATABASE_COUNT; ++i) |
366 | if (staging->services[i] == NULL) | |
367 | { | |
368 | ok = nss_database_select_default (&cache, i, | |
369 | &staging->services[i]); | |
370 | if (!ok) | |
371 | break; | |
372 | } | |
373 | } | |
374 | ||
375 | if (ok) | |
376 | ok = __file_change_detection_for_fp (&staging->nsswitch_conf, fp); | |
377 | ||
378 | if (fp != NULL) | |
379 | { | |
380 | int saved_errno = errno; | |
381 | fclose (fp); | |
382 | __set_errno (saved_errno); | |
383 | } | |
384 | ||
385 | if (ok && !__file_is_unchanged (&staging->nsswitch_conf, initial)) | |
386 | /* Reload is required because the file changed while reading. */ | |
387 | staging->nsswitch_conf.size = -1; | |
388 | ||
389 | return ok; | |
390 | } | |
391 | ||
392 | static bool | |
393 | nss_database_check_reload_and_get (struct nss_database_state *local, | |
394 | nss_action_list *result, | |
395 | enum nss_database database_index) | |
396 | { | |
52a5fe70 | 397 | struct __stat64_t64 str; |
429029a7 | 398 | |
fa78feca FW |
399 | /* Acquire MO is needed because the thread that sets reload_disabled |
400 | may have loaded the configuration first, so synchronize with the | |
401 | Release MO store there. */ | |
402 | if (atomic_load_acquire (&local->data.reload_disabled)) | |
f8847d83 DD |
403 | { |
404 | *result = local->data.services[database_index]; | |
405 | /* No reload, so there is no error. */ | |
406 | return true; | |
407 | } | |
fa78feca FW |
408 | |
409 | struct file_change_detection initial; | |
410 | if (!__file_change_detection_for_path (&initial, _PATH_NSSWITCH_CONF)) | |
411 | return false; | |
412 | ||
413 | __libc_lock_lock (local->lock); | |
414 | if (__file_is_unchanged (&initial, &local->data.nsswitch_conf)) | |
415 | { | |
416 | /* Configuration is up-to-date. Read it and return it to the | |
417 | caller. */ | |
418 | *result = local->data.services[database_index]; | |
419 | __libc_lock_unlock (local->lock); | |
420 | return true; | |
421 | } | |
429029a7 | 422 | |
ace9e3ed SJ |
423 | int stat_rv = __stat64_time64 ("/", &str); |
424 | ||
425 | if (local->data.services[database_index] != NULL) | |
429029a7 | 426 | { |
ace9e3ed SJ |
427 | /* Before we reload, verify that "/" hasn't changed. We assume that |
428 | errors here are very unlikely, but the chance that we're entering | |
429 | a container is also very unlikely, so we err on the side of both | |
430 | very unlikely things not happening at the same time. */ | |
431 | if (stat_rv != 0 | |
432 | || (local->root_ino != 0 | |
433 | && (str.st_ino != local->root_ino | |
434 | || str.st_dev != local->root_dev))) | |
435 | { | |
436 | /* Change detected; disable reloading and return current state. */ | |
437 | atomic_store_release (&local->data.reload_disabled, 1); | |
438 | *result = local->data.services[database_index]; | |
439 | __libc_lock_unlock (local->lock); | |
440 | return true; | |
441 | } | |
442 | } | |
443 | if (stat_rv == 0) | |
444 | { | |
445 | local->root_ino = str.st_ino; | |
446 | local->root_dev = str.st_dev; | |
429029a7 | 447 | } |
ace9e3ed | 448 | |
fa78feca FW |
449 | __libc_lock_unlock (local->lock); |
450 | ||
451 | /* Avoid overwriting the global configuration until we have loaded | |
452 | everything successfully. Otherwise, if the file change | |
453 | information changes back to what is in the global configuration, | |
454 | the lookups would use the partially-written configuration. */ | |
455 | struct nss_database_data staging = { .initialized = true, }; | |
456 | ||
457 | bool ok = nss_database_reload (&staging, &initial); | |
458 | ||
459 | if (ok) | |
460 | { | |
461 | __libc_lock_lock (local->lock); | |
462 | ||
463 | /* See above for memory order. */ | |
464 | if (!atomic_load_acquire (&local->data.reload_disabled)) | |
465 | /* This may go back in time if another thread beats this | |
466 | thread with the update, but in this case, a reload happens | |
467 | on the next NSS call. */ | |
468 | local->data = staging; | |
469 | ||
470 | *result = local->data.services[database_index]; | |
471 | __libc_lock_unlock (local->lock); | |
472 | } | |
473 | ||
474 | return ok; | |
475 | } | |
476 | ||
477 | bool | |
478 | __nss_database_get (enum nss_database db, nss_action_list *actions) | |
479 | { | |
480 | struct nss_database_state *local = nss_database_state_get (); | |
481 | return nss_database_check_reload_and_get (local, actions, db); | |
482 | } | |
9b456c5d | 483 | libc_hidden_def (__nss_database_get) |
fa78feca FW |
484 | |
485 | nss_action_list | |
486 | __nss_database_get_noreload (enum nss_database db) | |
487 | { | |
488 | /* There must have been a previous __nss_database_get call. */ | |
489 | struct nss_database_state *local = atomic_load_acquire (&global_database_state); | |
490 | assert (local != NULL); | |
491 | ||
492 | __libc_lock_lock (local->lock); | |
493 | nss_action_list result = local->data.services[db]; | |
494 | __libc_lock_unlock (local->lock); | |
495 | return result; | |
496 | } | |
497 | ||
88677348 | 498 | void |
fa78feca FW |
499 | __nss_database_freeres (void) |
500 | { | |
501 | free (global_database_state); | |
502 | global_database_state = NULL; | |
503 | } | |
504 | ||
505 | void | |
506 | __nss_database_fork_prepare_parent (struct nss_database_data *data) | |
507 | { | |
508 | /* Do not use allocate_once to trigger loading unnecessarily. */ | |
509 | struct nss_database_state *local = atomic_load_acquire (&global_database_state); | |
510 | if (local == NULL) | |
511 | data->initialized = false; | |
512 | else | |
513 | { | |
514 | /* Make a copy of the configuration. This approach was chosen | |
515 | because it avoids acquiring the lock during the actual | |
516 | fork. */ | |
517 | __libc_lock_lock (local->lock); | |
518 | *data = local->data; | |
519 | __libc_lock_unlock (local->lock); | |
520 | } | |
521 | } | |
522 | ||
523 | void | |
524 | __nss_database_fork_subprocess (struct nss_database_data *data) | |
525 | { | |
526 | struct nss_database_state *local = atomic_load_acquire (&global_database_state); | |
527 | if (data->initialized) | |
528 | { | |
529 | /* Restore the state at the point of the fork. */ | |
530 | assert (local != NULL); | |
531 | local->data = *data; | |
532 | __libc_lock_init (local->lock); | |
533 | } | |
534 | else if (local != NULL) | |
535 | /* The NSS configuration was loaded concurrently during fork. We | |
536 | do not know its state, so we need to discard it. */ | |
537 | global_database_state = NULL; | |
538 | } |