]> git.ipfire.org Git - thirdparty/glibc.git/blame - nss/nss_database.c
Update copyright dates with scripts/update-copyrights
[thirdparty/glibc.git] / nss / nss_database.c
CommitLineData
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
32struct 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 *". */
42static void *global_database_state;
43
44/* Allocate and return pointer to nss_database_state object or
45 on failure return NULL. */
46static void *
47global_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. */
63static struct nss_database_state *
64nss_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. */
72enum 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. */
86static 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
98struct nss_database_default_cache
99{
100 nss_action_list caches[NSS_DATABASE_DEFAULT_COUNT];
101};
102
103static bool
104nss_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. */
168typedef 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
174static const database_name nss_database_name_array[] =
175 {
176#define DEFINE_DATABASE(name) #name,
177#include "databases.def"
178#undef DEFINE_DATABASE
179 };
180
181static int
182name_search (const void *left, const void *right)
183{
184 return strcmp (left, right);
185}
186
187static int
188name_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
198static bool
199process_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
230int
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. */
265static bool
266nss_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
293static bool
294nss_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
354static bool
355nss_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
410bool
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
417nss_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
430void __libc_freeres_fn_section
431__nss_database_freeres (void)
432{
433 free (global_database_state);
434 global_database_state = NULL;
435}
436
437void
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
455void
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}