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