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