]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/nss-systemd/nss-systemd.c
Merge pull request #17185 from yuwata/ethtool-update
[thirdparty/systemd.git] / src / nss-systemd / nss-systemd.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <nss.h>
4 #include <pthread.h>
5
6 #include "env-util.h"
7 #include "errno-util.h"
8 #include "fd-util.h"
9 #include "macro.h"
10 #include "nss-systemd.h"
11 #include "nss-util.h"
12 #include "pthread-util.h"
13 #include "signal-util.h"
14 #include "strv.h"
15 #include "user-record-nss.h"
16 #include "user-util.h"
17 #include "userdb-glue.h"
18 #include "userdb.h"
19
20 static const struct passwd root_passwd = {
21 .pw_name = (char*) "root",
22 .pw_passwd = (char*) "x", /* see shadow file */
23 .pw_uid = 0,
24 .pw_gid = 0,
25 .pw_gecos = (char*) "Super User",
26 .pw_dir = (char*) "/root",
27 .pw_shell = (char*) "/bin/sh",
28 };
29
30 static const struct passwd nobody_passwd = {
31 .pw_name = (char*) NOBODY_USER_NAME,
32 .pw_passwd = (char*) "*", /* locked */
33 .pw_uid = UID_NOBODY,
34 .pw_gid = GID_NOBODY,
35 .pw_gecos = (char*) "User Nobody",
36 .pw_dir = (char*) "/",
37 .pw_shell = (char*) NOLOGIN,
38 };
39
40 static const struct group root_group = {
41 .gr_name = (char*) "root",
42 .gr_gid = 0,
43 .gr_passwd = (char*) "x", /* see shadow file */
44 .gr_mem = (char*[]) { NULL },
45 };
46
47 static const struct group nobody_group = {
48 .gr_name = (char*) NOBODY_GROUP_NAME,
49 .gr_gid = GID_NOBODY,
50 .gr_passwd = (char*) "*", /* locked */
51 .gr_mem = (char*[]) { NULL },
52 };
53
54 typedef struct GetentData {
55 /* As explained in NOTES section of getpwent_r(3) as 'getpwent_r() is not really reentrant since it
56 * shares the reading position in the stream with all other threads', we need to protect the data in
57 * UserDBIterator from multithreaded programs which may call setpwent(), getpwent_r(), or endpwent()
58 * simultaneously. So, each function locks the data by using the mutex below. */
59 pthread_mutex_t mutex;
60 UserDBIterator *iterator;
61
62 /* Applies to group iterations only: true while we iterate over groups defined through NSS, false
63 * otherwise. */
64 bool by_membership;
65 } GetentData;
66
67 static GetentData getpwent_data = {
68 .mutex = PTHREAD_MUTEX_INITIALIZER
69 };
70
71 static GetentData getgrent_data = {
72 .mutex = PTHREAD_MUTEX_INITIALIZER
73 };
74
75 NSS_GETPW_PROTOTYPES(systemd);
76 NSS_GETGR_PROTOTYPES(systemd);
77 NSS_PWENT_PROTOTYPES(systemd);
78 NSS_GRENT_PROTOTYPES(systemd);
79 NSS_INITGROUPS_PROTOTYPE(systemd);
80
81 enum nss_status _nss_systemd_getpwnam_r(
82 const char *name,
83 struct passwd *pwd,
84 char *buffer, size_t buflen,
85 int *errnop) {
86
87 enum nss_status status;
88 int e;
89
90 PROTECT_ERRNO;
91 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
92
93 assert(name);
94 assert(pwd);
95 assert(errnop);
96
97 /* If the username is not valid, then we don't know it. Ideally libc would filter these for us
98 * anyway. We don't generate EINVAL here, because it isn't really out business to complain about
99 * invalid user names. */
100 if (!valid_user_group_name(name, VALID_USER_RELAX))
101 return NSS_STATUS_NOTFOUND;
102
103 /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
104 if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
105
106 if (streq(name, root_passwd.pw_name)) {
107 *pwd = root_passwd;
108 return NSS_STATUS_SUCCESS;
109 }
110
111 if (streq(name, nobody_passwd.pw_name)) {
112 if (!synthesize_nobody())
113 return NSS_STATUS_NOTFOUND;
114
115 *pwd = nobody_passwd;
116 return NSS_STATUS_SUCCESS;
117 }
118
119 } else if (STR_IN_SET(name, root_passwd.pw_name, nobody_passwd.pw_name))
120 return NSS_STATUS_NOTFOUND;
121
122 status = userdb_getpwnam(name, pwd, buffer, buflen, &e);
123 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
124 UNPROTECT_ERRNO;
125 *errnop = e;
126 return status;
127 }
128
129 return status;
130 }
131
132 enum nss_status _nss_systemd_getpwuid_r(
133 uid_t uid,
134 struct passwd *pwd,
135 char *buffer, size_t buflen,
136 int *errnop) {
137
138 enum nss_status status;
139 int e;
140
141 PROTECT_ERRNO;
142 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
143
144 assert(pwd);
145 assert(errnop);
146
147 if (!uid_is_valid(uid))
148 return NSS_STATUS_NOTFOUND;
149
150 /* Synthesize data for the root user and for nobody in case they are missing from /etc/passwd */
151 if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
152
153 if (uid == root_passwd.pw_uid) {
154 *pwd = root_passwd;
155 return NSS_STATUS_SUCCESS;
156 }
157
158 if (uid == nobody_passwd.pw_uid) {
159 if (!synthesize_nobody())
160 return NSS_STATUS_NOTFOUND;
161
162 *pwd = nobody_passwd;
163 return NSS_STATUS_SUCCESS;
164 }
165
166 } else if (uid == root_passwd.pw_uid || uid == nobody_passwd.pw_uid)
167 return NSS_STATUS_NOTFOUND;
168
169 status = userdb_getpwuid(uid, pwd, buffer, buflen, &e);
170 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
171 UNPROTECT_ERRNO;
172 *errnop = e;
173 return status;
174 }
175
176 return status;
177 }
178
179 #pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
180
181 enum nss_status _nss_systemd_getgrnam_r(
182 const char *name,
183 struct group *gr,
184 char *buffer, size_t buflen,
185 int *errnop) {
186
187 enum nss_status status;
188 int e;
189
190 PROTECT_ERRNO;
191 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
192
193 assert(name);
194 assert(gr);
195 assert(errnop);
196
197 if (!valid_user_group_name(name, VALID_USER_RELAX))
198 return NSS_STATUS_NOTFOUND;
199
200 /* Synthesize records for root and nobody, in case they are missing from /etc/group */
201 if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
202
203 if (streq(name, root_group.gr_name)) {
204 *gr = root_group;
205 return NSS_STATUS_SUCCESS;
206 }
207
208 if (streq(name, nobody_group.gr_name)) {
209 if (!synthesize_nobody())
210 return NSS_STATUS_NOTFOUND;
211
212 *gr = nobody_group;
213 return NSS_STATUS_SUCCESS;
214 }
215
216 } else if (STR_IN_SET(name, root_group.gr_name, nobody_group.gr_name))
217 return NSS_STATUS_NOTFOUND;
218
219 status = userdb_getgrnam(name, gr, buffer, buflen, &e);
220 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
221 UNPROTECT_ERRNO;
222 *errnop = e;
223 return status;
224 }
225
226 return status;
227 }
228
229 enum nss_status _nss_systemd_getgrgid_r(
230 gid_t gid,
231 struct group *gr,
232 char *buffer, size_t buflen,
233 int *errnop) {
234
235 enum nss_status status;
236 int e;
237
238 PROTECT_ERRNO;
239 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
240
241 assert(gr);
242 assert(errnop);
243
244 if (!gid_is_valid(gid))
245 return NSS_STATUS_NOTFOUND;
246
247 /* Synthesize records for root and nobody, in case they are missing from /etc/group */
248 if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
249
250 if (gid == root_group.gr_gid) {
251 *gr = root_group;
252 return NSS_STATUS_SUCCESS;
253 }
254
255 if (gid == nobody_group.gr_gid) {
256 if (!synthesize_nobody())
257 return NSS_STATUS_NOTFOUND;
258
259 *gr = nobody_group;
260 return NSS_STATUS_SUCCESS;
261 }
262
263 } else if (gid == root_group.gr_gid || gid == nobody_group.gr_gid)
264 return NSS_STATUS_NOTFOUND;
265
266 status = userdb_getgrgid(gid, gr, buffer, buflen, &e);
267 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
268 UNPROTECT_ERRNO;
269 *errnop = e;
270 return status;
271 }
272
273 return status;
274 }
275
276 static enum nss_status nss_systemd_endent(GetentData *p) {
277 PROTECT_ERRNO;
278 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
279
280 assert(p);
281
282 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = NULL;
283 _l = pthread_mutex_lock_assert(&p->mutex);
284
285 p->iterator = userdb_iterator_free(p->iterator);
286 p->by_membership = false;
287
288 return NSS_STATUS_SUCCESS;
289 }
290
291 enum nss_status _nss_systemd_endpwent(void) {
292 return nss_systemd_endent(&getpwent_data);
293 }
294
295 enum nss_status _nss_systemd_endgrent(void) {
296 return nss_systemd_endent(&getgrent_data);
297 }
298
299 enum nss_status _nss_systemd_setpwent(int stayopen) {
300 PROTECT_ERRNO;
301 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
302
303 if (_nss_systemd_is_blocked())
304 return NSS_STATUS_NOTFOUND;
305
306 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = NULL;
307 int r;
308
309 _l = pthread_mutex_lock_assert(&getpwent_data.mutex);
310
311 getpwent_data.iterator = userdb_iterator_free(getpwent_data.iterator);
312 getpwent_data.by_membership = false;
313
314 /* Don't synthesize root/nobody when iterating. Let nss-files take care of that. If the two records
315 * are missing there, then that's fine, after all getpwent() is known to be possibly incomplete
316 * (think: LDAP/NIS type situations), and our synthesizing of root/nobody is a robustness fallback
317 * only, which matters for getpwnam()/getpwuid() primarily, which are the main NSS entrypoints to the
318 * user database. */
319 r = userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getpwent_data.iterator);
320 return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
321 }
322
323 enum nss_status _nss_systemd_setgrent(int stayopen) {
324 PROTECT_ERRNO;
325 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
326
327 if (_nss_systemd_is_blocked())
328 return NSS_STATUS_NOTFOUND;
329
330 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = NULL;
331 int r;
332
333 _l = pthread_mutex_lock_assert(&getgrent_data.mutex);
334
335 getgrent_data.iterator = userdb_iterator_free(getgrent_data.iterator);
336 getpwent_data.by_membership = false;
337
338 /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
339 r = groupdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE, &getgrent_data.iterator);
340 return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
341 }
342
343 enum nss_status _nss_systemd_getpwent_r(
344 struct passwd *result,
345 char *buffer, size_t buflen,
346 int *errnop) {
347
348 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
349 int r;
350
351 PROTECT_ERRNO;
352 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
353
354 assert(result);
355 assert(errnop);
356
357 if (_nss_systemd_is_blocked())
358 return NSS_STATUS_NOTFOUND;
359
360 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = NULL;
361
362 _l = pthread_mutex_lock_assert(&getpwent_data.mutex);
363
364 if (!getpwent_data.iterator) {
365 UNPROTECT_ERRNO;
366 *errnop = EHOSTDOWN;
367 return NSS_STATUS_UNAVAIL;
368 }
369
370 r = userdb_iterator_get(getpwent_data.iterator, &ur);
371 if (r == -ESRCH)
372 return NSS_STATUS_NOTFOUND;
373 if (r < 0) {
374 UNPROTECT_ERRNO;
375 *errnop = -r;
376 return NSS_STATUS_UNAVAIL;
377 }
378
379 r = nss_pack_user_record(ur, result, buffer, buflen);
380 if (r < 0) {
381 UNPROTECT_ERRNO;
382 *errnop = -r;
383 return NSS_STATUS_TRYAGAIN;
384 }
385
386 return NSS_STATUS_SUCCESS;
387 }
388
389 enum nss_status _nss_systemd_getgrent_r(
390 struct group *result,
391 char *buffer, size_t buflen,
392 int *errnop) {
393
394 _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
395 _cleanup_free_ char **members = NULL;
396 int r;
397
398 PROTECT_ERRNO;
399 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
400
401 assert(result);
402 assert(errnop);
403
404 if (_nss_systemd_is_blocked())
405 return NSS_STATUS_NOTFOUND;
406
407 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = NULL;
408
409 _l = pthread_mutex_lock_assert(&getgrent_data.mutex);
410
411 if (!getgrent_data.iterator) {
412 UNPROTECT_ERRNO;
413 *errnop = EHOSTDOWN;
414 return NSS_STATUS_UNAVAIL;
415 }
416
417 if (!getgrent_data.by_membership) {
418 r = groupdb_iterator_get(getgrent_data.iterator, &gr);
419 if (r == -ESRCH) {
420 /* So we finished iterating native groups now. Let's now continue with iterating
421 * native memberships, and generate additional group entries for any groups
422 * referenced there that are defined in NSS only. This means for those groups there
423 * will be two or more entries generated during iteration, but this is apparently how
424 * this is supposed to work, and what other implementations do too. Clients are
425 * supposed to merge the group records found during iteration automatically. */
426 getgrent_data.iterator = userdb_iterator_free(getgrent_data.iterator);
427
428 r = membershipdb_all(nss_glue_userdb_flags(), &getgrent_data.iterator);
429 if (r < 0) {
430 UNPROTECT_ERRNO;
431 *errnop = -r;
432 return NSS_STATUS_UNAVAIL;
433 }
434
435 getgrent_data.by_membership = true;
436 } else if (r < 0) {
437 UNPROTECT_ERRNO;
438 *errnop = -r;
439 return NSS_STATUS_UNAVAIL;
440 } else if (!STR_IN_SET(gr->group_name, root_group.gr_name, nobody_group.gr_name)) {
441 r = membershipdb_by_group_strv(gr->group_name, nss_glue_userdb_flags(), &members);
442 if (r < 0) {
443 UNPROTECT_ERRNO;
444 *errnop = -r;
445 return NSS_STATUS_UNAVAIL;
446 }
447 }
448 }
449
450 if (getgrent_data.by_membership) {
451 _cleanup_(_nss_systemd_unblockp) bool blocked = false;
452
453 for (;;) {
454 _cleanup_free_ char *user_name = NULL, *group_name = NULL;
455
456 r = membershipdb_iterator_get(getgrent_data.iterator, &user_name, &group_name);
457 if (r == -ESRCH)
458 return NSS_STATUS_NOTFOUND;
459 if (r < 0) {
460 UNPROTECT_ERRNO;
461 *errnop = -r;
462 return NSS_STATUS_UNAVAIL;
463 }
464
465 if (STR_IN_SET(user_name, root_passwd.pw_name, nobody_passwd.pw_name))
466 continue;
467 if (STR_IN_SET(group_name, root_group.gr_name, nobody_group.gr_name))
468 continue;
469
470 /* We are about to recursively call into NSS, let's make sure we disable recursion into our own code. */
471 if (!blocked) {
472 r = _nss_systemd_block(true);
473 if (r < 0) {
474 UNPROTECT_ERRNO;
475 *errnop = -r;
476 return NSS_STATUS_UNAVAIL;
477 }
478
479 blocked = true;
480 }
481
482 r = nss_group_record_by_name(group_name, false, &gr);
483 if (r == -ESRCH)
484 continue;
485 if (r < 0) {
486 log_debug_errno(r, "Failed to do NSS check for group '%s', ignoring: %m", group_name);
487 continue;
488 }
489
490 members = strv_new(user_name);
491 if (!members) {
492 UNPROTECT_ERRNO;
493 *errnop = ENOMEM;
494 return NSS_STATUS_TRYAGAIN;
495 }
496
497 /* Note that we currently generate one group entry per user that is part of a
498 * group. It's a bit ugly, but equivalent to generating a single entry with a set of
499 * members in them. */
500 break;
501 }
502 }
503
504 r = nss_pack_group_record(gr, members, result, buffer, buflen);
505 if (r < 0) {
506 UNPROTECT_ERRNO;
507 *errnop = -r;
508 return NSS_STATUS_TRYAGAIN;
509 }
510
511 return NSS_STATUS_SUCCESS;
512 }
513
514 enum nss_status _nss_systemd_initgroups_dyn(
515 const char *user_name,
516 gid_t gid,
517 long *start,
518 long *size,
519 gid_t **groupsp,
520 long int limit,
521 int *errnop) {
522
523 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
524 bool any = false;
525 int r;
526
527 PROTECT_ERRNO;
528 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK);
529
530 assert(user_name);
531 assert(start);
532 assert(size);
533 assert(groupsp);
534 assert(errnop);
535
536 if (!valid_user_group_name(user_name, VALID_USER_RELAX))
537 return NSS_STATUS_NOTFOUND;
538
539 /* Don't allow extending these two special users, the same as we won't resolve them via getpwnam() */
540 if (STR_IN_SET(user_name, root_passwd.pw_name, nobody_passwd.pw_name))
541 return NSS_STATUS_NOTFOUND;
542
543 if (_nss_systemd_is_blocked())
544 return NSS_STATUS_NOTFOUND;
545
546 r = membershipdb_by_user(user_name, nss_glue_userdb_flags(), &iterator);
547 if (r < 0) {
548 UNPROTECT_ERRNO;
549 *errnop = -r;
550 return NSS_STATUS_UNAVAIL;
551 }
552
553 for (;;) {
554 _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
555 _cleanup_free_ char *group_name = NULL;
556
557 r = membershipdb_iterator_get(iterator, NULL, &group_name);
558 if (r == -ESRCH)
559 break;
560 if (r < 0) {
561 UNPROTECT_ERRNO;
562 *errnop = -r;
563 return NSS_STATUS_UNAVAIL;
564 }
565
566 /* The group might be defined via traditional NSS only, hence let's do a full look-up without
567 * disabling NSS. This means we are operating recursively here. */
568
569 r = groupdb_by_name(group_name, (nss_glue_userdb_flags() & ~USERDB_AVOID_NSS) | USERDB_AVOID_SHADOW, &g);
570 if (r == -ESRCH)
571 continue;
572 if (r < 0) {
573 log_debug_errno(r, "Failed to resolve group '%s', ignoring: %m", group_name);
574 continue;
575 }
576
577 if (g->gid == gid)
578 continue;
579
580 if (*start >= *size) {
581 gid_t *new_groups;
582 long new_size;
583
584 if (limit > 0 && *size >= limit) /* Reached the limit.? */
585 break;
586
587 if (*size > LONG_MAX/2) { /* Check for overflow */
588 UNPROTECT_ERRNO;
589 *errnop = ENOMEM;
590 return NSS_STATUS_TRYAGAIN;
591 }
592
593 new_size = *start * 2;
594 if (limit > 0 && new_size > limit)
595 new_size = limit;
596
597 /* Enlarge buffer */
598 new_groups = reallocarray(*groupsp, new_size, sizeof(**groupsp));
599 if (!new_groups) {
600 UNPROTECT_ERRNO;
601 *errnop = ENOMEM;
602 return NSS_STATUS_TRYAGAIN;
603 }
604
605 *groupsp = new_groups;
606 *size = new_size;
607 }
608
609 (*groupsp)[(*start)++] = g->gid;
610 any = true;
611 }
612
613 return any ? NSS_STATUS_SUCCESS : NSS_STATUS_NOTFOUND;
614 }
615
616 static thread_local unsigned _blocked = 0;
617
618 _public_ int _nss_systemd_block(bool b) {
619
620 /* This blocks recursively: it's blocked for as many times this function is called with `true` until
621 * it is called an equal time with `false`. */
622
623 if (b) {
624 if (_blocked >= UINT_MAX)
625 return -EOVERFLOW;
626
627 _blocked++;
628 } else {
629 if (_blocked <= 0)
630 return -EOVERFLOW;
631
632 _blocked--;
633 }
634
635 return b; /* Return what is passed in, i.e. the new state from the PoV of the caller */
636 }
637
638 _public_ bool _nss_systemd_is_blocked(void) {
639 return _blocked > 0;
640 }