]> git.ipfire.org Git - thirdparty/glibc.git/blob - nss/nss_database.c
6ff537152f5a99d73ede3b369e011b2fe9759ac7
[thirdparty/glibc.git] / nss / nss_database.c
1 /* Mapping NSS services to action lists.
2 Copyright (C) 2020 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 "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;
215 *line++ = '\0';
216
217 int db = name_to_database_index (name);
218 if (db < 0)
219 /* Not our database e.g. sudoers, automount, etc. */
220 return true;
221
222 nss_action_list result = __nss_action_parse (line);
223 if (result == NULL)
224 return false;
225 data->services[db] = result;
226 return true;
227 }
228
229 int
230 __nss_configure_lookup (const char *dbname, const char *service_line)
231 {
232 int db;
233 nss_action_list result;
234 struct nss_database_state *local;
235
236 /* Convert named database to index. */
237 db = name_to_database_index (dbname);
238 if (db < 0)
239 /* Not our database (e.g., sudoers). */
240 return -1;
241
242 /* Force any load/cache/read whatever to happen, so we can override
243 it. */
244 __nss_database_get (db, &result);
245
246 local = nss_database_state_get ();
247
248 result = __nss_action_parse (service_line);
249 if (result == NULL)
250 return -1;
251
252 atomic_store_release (&local->data.reload_disabled, 1);
253 local->data.services[db] = result;
254
255 #ifdef USE_NSCD
256 __nss_database_custom[db] = true;
257 #endif
258
259 return 0;
260 }
261
262 /* Iterate over the lines in FP, parse them, and store them in DATA.
263 Return false on memory allocation failure, true on success. */
264 static bool
265 nss_database_reload_1 (struct nss_database_data *data, FILE *fp)
266 {
267 char *line = NULL;
268 size_t line_allocated = 0;
269 bool result = false;
270
271 while (true)
272 {
273 ssize_t ret = __getline (&line, &line_allocated, fp);
274 if (__ferror_unlocked (fp))
275 break;
276 if (__feof_unlocked (fp))
277 {
278 result = true;
279 break;
280 }
281 assert (ret > 0);
282 (void) ret; /* For NDEBUG builds. */
283
284 if (!process_line (data, line))
285 break;
286 }
287
288 free (line);
289 return result;
290 }
291
292 static bool
293 nss_database_reload (struct nss_database_data *staging,
294 struct file_change_detection *initial)
295 {
296 FILE *fp = fopen (_PATH_NSSWITCH_CONF, "rce");
297 if (fp == NULL)
298 switch (errno)
299 {
300 case EACCES:
301 case EISDIR:
302 case ELOOP:
303 case ENOENT:
304 case ENOTDIR:
305 case EPERM:
306 /* Ignore these errors. They are persistent errors caused
307 by file system contents. */
308 break;
309 default:
310 /* Other errors refer to resource allocation problems and
311 need to be handled by the application. */
312 return false;
313 }
314 else
315 /* No other threads have access to fp. */
316 __fsetlocking (fp, FSETLOCKING_BYCALLER);
317
318 bool ok = true;
319 if (fp != NULL)
320 ok = nss_database_reload_1 (staging, fp);
321
322 /* Apply defaults. */
323 if (ok)
324 {
325 struct nss_database_default_cache cache = { };
326 for (int i = 0; i < NSS_DATABASE_COUNT; ++i)
327 if (staging->services[i] == NULL)
328 {
329 ok = nss_database_select_default (&cache, i,
330 &staging->services[i]);
331 if (!ok)
332 break;
333 }
334 }
335
336 if (ok)
337 ok = __file_change_detection_for_fp (&staging->nsswitch_conf, fp);
338
339 if (fp != NULL)
340 {
341 int saved_errno = errno;
342 fclose (fp);
343 __set_errno (saved_errno);
344 }
345
346 if (ok && !__file_is_unchanged (&staging->nsswitch_conf, initial))
347 /* Reload is required because the file changed while reading. */
348 staging->nsswitch_conf.size = -1;
349
350 return ok;
351 }
352
353 static bool
354 nss_database_check_reload_and_get (struct nss_database_state *local,
355 nss_action_list *result,
356 enum nss_database database_index)
357 {
358 /* Acquire MO is needed because the thread that sets reload_disabled
359 may have loaded the configuration first, so synchronize with the
360 Release MO store there. */
361 if (atomic_load_acquire (&local->data.reload_disabled))
362 {
363 *result = local->data.services[database_index];
364 /* No reload, so there is no error. */
365 return true;
366 }
367
368 struct file_change_detection initial;
369 if (!__file_change_detection_for_path (&initial, _PATH_NSSWITCH_CONF))
370 return false;
371
372 __libc_lock_lock (local->lock);
373 if (__file_is_unchanged (&initial, &local->data.nsswitch_conf))
374 {
375 /* Configuration is up-to-date. Read it and return it to the
376 caller. */
377 *result = local->data.services[database_index];
378 __libc_lock_unlock (local->lock);
379 return true;
380 }
381 __libc_lock_unlock (local->lock);
382
383 /* Avoid overwriting the global configuration until we have loaded
384 everything successfully. Otherwise, if the file change
385 information changes back to what is in the global configuration,
386 the lookups would use the partially-written configuration. */
387 struct nss_database_data staging = { .initialized = true, };
388
389 bool ok = nss_database_reload (&staging, &initial);
390
391 if (ok)
392 {
393 __libc_lock_lock (local->lock);
394
395 /* See above for memory order. */
396 if (!atomic_load_acquire (&local->data.reload_disabled))
397 /* This may go back in time if another thread beats this
398 thread with the update, but in this case, a reload happens
399 on the next NSS call. */
400 local->data = staging;
401
402 *result = local->data.services[database_index];
403 __libc_lock_unlock (local->lock);
404 }
405
406 return ok;
407 }
408
409 bool
410 __nss_database_get (enum nss_database db, nss_action_list *actions)
411 {
412 struct nss_database_state *local = nss_database_state_get ();
413 return nss_database_check_reload_and_get (local, actions, db);
414 }
415
416 nss_action_list
417 __nss_database_get_noreload (enum nss_database db)
418 {
419 /* There must have been a previous __nss_database_get call. */
420 struct nss_database_state *local = atomic_load_acquire (&global_database_state);
421 assert (local != NULL);
422
423 __libc_lock_lock (local->lock);
424 nss_action_list result = local->data.services[db];
425 __libc_lock_unlock (local->lock);
426 return result;
427 }
428
429 void __libc_freeres_fn_section
430 __nss_database_freeres (void)
431 {
432 free (global_database_state);
433 global_database_state = NULL;
434 }
435
436 void
437 __nss_database_fork_prepare_parent (struct nss_database_data *data)
438 {
439 /* Do not use allocate_once to trigger loading unnecessarily. */
440 struct nss_database_state *local = atomic_load_acquire (&global_database_state);
441 if (local == NULL)
442 data->initialized = false;
443 else
444 {
445 /* Make a copy of the configuration. This approach was chosen
446 because it avoids acquiring the lock during the actual
447 fork. */
448 __libc_lock_lock (local->lock);
449 *data = local->data;
450 __libc_lock_unlock (local->lock);
451 }
452 }
453
454 void
455 __nss_database_fork_subprocess (struct nss_database_data *data)
456 {
457 struct nss_database_state *local = atomic_load_acquire (&global_database_state);
458 if (data->initialized)
459 {
460 /* Restore the state at the point of the fork. */
461 assert (local != NULL);
462 local->data = *data;
463 __libc_lock_init (local->lock);
464 }
465 else if (local != NULL)
466 /* The NSS configuration was loaded concurrently during fork. We
467 do not know its state, so we need to discard it. */
468 global_database_state = NULL;
469 }