]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/nss-systemd/nss-systemd.c
fix: UnsetProperty example in systemd.link.xml
[thirdparty/systemd.git] / src / nss-systemd / nss-systemd.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
409093fe 2
ca00c939 3#include <gshadow.h>
409093fe 4#include <nss.h>
12c2c56d 5#include <pthread.h>
47fd7fa6 6#include <string.h>
e7537295 7#include <threads.h>
409093fe 8
6553db60 9#include "alloc-util.h"
409093fe 10#include "env-util.h"
1684c56f 11#include "errno-util.h"
3339381f 12#include "log.h"
037b0a47 13#include "nss-systemd.h"
409093fe 14#include "nss-util.h"
37bc9dcc 15#include "pthread-util.h"
409093fe 16#include "signal-util.h"
ca00c939 17#include "string-util.h"
1684c56f 18#include "strv.h"
e60775cb 19#include "user-record-nss.h"
409093fe 20#include "user-util.h"
1684c56f 21#include "userdb.h"
1cf40697 22#include "userdb-glue.h"
9b5eaef3 23
2129011e
LP
24static const struct passwd root_passwd = {
25 .pw_name = (char*) "root",
53c25ac9 26 .pw_passwd = (char*) PASSWORD_SEE_SHADOW,
2129011e
LP
27 .pw_uid = 0,
28 .pw_gid = 0,
29 .pw_gecos = (char*) "Super User",
30 .pw_dir = (char*) "/root",
8a7adccb 31 .pw_shell = NULL,
2129011e
LP
32};
33
f43a19ec
LP
34static const struct spwd root_spwd = {
35 .sp_namp = (char*) "root",
36 .sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID,
37 .sp_lstchg = -1,
38 .sp_min = -1,
39 .sp_max = -1,
40 .sp_warn = -1,
41 .sp_inact = -1,
42 .sp_expire = -1,
43 .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
44};
45
2129011e
LP
46static const struct passwd nobody_passwd = {
47 .pw_name = (char*) NOBODY_USER_NAME,
53c25ac9 48 .pw_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
3a664727
LP
49 .pw_uid = UID_NOBODY,
50 .pw_gid = GID_NOBODY,
94b84a07 51 .pw_gecos = (char*) "Kernel Overflow User",
2129011e 52 .pw_dir = (char*) "/",
6db90462 53 .pw_shell = (char*) NOLOGIN,
2129011e
LP
54};
55
f43a19ec
LP
56static const struct spwd nobody_spwd = {
57 .sp_namp = (char*) NOBODY_USER_NAME,
58 .sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID,
59 .sp_lstchg = -1,
60 .sp_min = -1,
61 .sp_max = -1,
62 .sp_warn = -1,
63 .sp_inact = -1,
64 .sp_expire = -1,
65 .sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
66};
67
2129011e
LP
68static const struct group root_group = {
69 .gr_name = (char*) "root",
70 .gr_gid = 0,
53c25ac9 71 .gr_passwd = (char*) PASSWORD_SEE_SHADOW,
2129011e
LP
72 .gr_mem = (char*[]) { NULL },
73};
74
f43a19ec
LP
75static const struct sgrp root_sgrp = {
76 .sg_namp = (char*) "root",
77 .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
78};
79
2129011e
LP
80static const struct group nobody_group = {
81 .gr_name = (char*) NOBODY_GROUP_NAME,
3a664727 82 .gr_gid = GID_NOBODY,
53c25ac9 83 .gr_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
2129011e
LP
84 .gr_mem = (char*[]) { NULL },
85};
86
f43a19ec
LP
87static const struct sgrp nobody_sgrp = {
88 .sg_namp = (char*) NOBODY_GROUP_NAME,
89 .sg_passwd = (char*) PASSWORD_LOCKED_AND_INVALID,
90};
91
1684c56f
LP
92typedef struct GetentData {
93 /* As explained in NOTES section of getpwent_r(3) as 'getpwent_r() is not really reentrant since it
94 * shares the reading position in the stream with all other threads', we need to protect the data in
95 * UserDBIterator from multithreaded programs which may call setpwent(), getpwent_r(), or endpwent()
96 * simultaneously. So, each function locks the data by using the mutex below. */
97 pthread_mutex_t mutex;
98 UserDBIterator *iterator;
12c2c56d 99
1684c56f
LP
100 /* Applies to group iterations only: true while we iterate over groups defined through NSS, false
101 * otherwise. */
102 bool by_membership;
103} GetentData;
12c2c56d 104
83a3018e
LP
105/* On current glibc PTHREAD_MUTEX_INITIALIZER is defined in a way incompatible with
106 * -Wzero-as-null-pointer-constant, work around this for now. */
107DISABLE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT;
1684c56f 108static GetentData getpwent_data = {
f43a19ec 109 .mutex = PTHREAD_MUTEX_INITIALIZER,
12c2c56d
YW
110};
111
1684c56f 112static GetentData getgrent_data = {
f43a19ec
LP
113 .mutex = PTHREAD_MUTEX_INITIALIZER,
114};
115
116static GetentData getspent_data = {
117 .mutex = PTHREAD_MUTEX_INITIALIZER,
118};
119
120static GetentData getsgent_data = {
121 .mutex = PTHREAD_MUTEX_INITIALIZER,
12c2c56d 122};
83a3018e 123REENABLE_WARNING;
12c2c56d 124
3339381f
ZJS
125static void setup_logging_once(void) {
126 static pthread_once_t once = PTHREAD_ONCE_INIT;
a7d15a24 127 assert_se(pthread_once(&once, log_parse_environment_variables) == 0);
3339381f
ZJS
128}
129
130#define NSS_ENTRYPOINT_BEGIN \
131 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); \
132 setup_logging_once()
133
409093fe 134NSS_GETPW_PROTOTYPES(systemd);
f43a19ec 135NSS_GETSP_PROTOTYPES(systemd);
409093fe 136NSS_GETGR_PROTOTYPES(systemd);
f43a19ec 137NSS_GETSG_PROTOTYPES(systemd);
1684c56f 138NSS_PWENT_PROTOTYPES(systemd);
f43a19ec 139NSS_SPENT_PROTOTYPES(systemd);
1684c56f 140NSS_GRENT_PROTOTYPES(systemd);
f43a19ec 141NSS_SGENT_PROTOTYPES(systemd);
1684c56f 142NSS_INITGROUPS_PROTOTYPE(systemd);
fd63e712 143
47fd7fa6
MC
144/* Since our NSS functions implement reentrant glibc APIs, we have to guarantee
145 * all the string pointers we return point into the buffer provided by the
146 * caller, not into our own static memory. */
147
148static enum nss_status copy_synthesized_passwd(
149 struct passwd *dest,
150 const struct passwd *src,
8a7adccb 151 const char *fallback_shell,
47fd7fa6
MC
152 char *buffer, size_t buflen,
153 int *errnop) {
154
47fd7fa6
MC
155 assert(dest);
156 assert(src);
157 assert(src->pw_name);
158 assert(src->pw_passwd);
159 assert(src->pw_gecos);
160 assert(src->pw_dir);
47fd7fa6 161
8a7adccb
ZJS
162 const char *shell = ASSERT_PTR(src->pw_shell ?: fallback_shell);
163
164 size_t required =
165 strlen(src->pw_name) + 1 +
166 strlen(src->pw_passwd) + 1 +
167 strlen(src->pw_gecos) + 1 +
168 strlen(src->pw_dir) + 1 +
169 strlen(shell) + 1;
47fd7fa6
MC
170
171 if (buflen < required) {
172 *errnop = ERANGE;
173 return NSS_STATUS_TRYAGAIN;
174 }
175
176 assert(buffer);
177
178 *dest = *src;
179
180 /* String fields point into the user-provided buffer */
181 dest->pw_name = buffer;
182 dest->pw_passwd = stpcpy(dest->pw_name, src->pw_name) + 1;
183 dest->pw_gecos = stpcpy(dest->pw_passwd, src->pw_passwd) + 1;
184 dest->pw_dir = stpcpy(dest->pw_gecos, src->pw_gecos) + 1;
185 dest->pw_shell = stpcpy(dest->pw_dir, src->pw_dir) + 1;
8a7adccb 186 strcpy(dest->pw_shell, shell);
47fd7fa6
MC
187
188 return NSS_STATUS_SUCCESS;
189}
190
191static enum nss_status copy_synthesized_spwd(
192 struct spwd *dest,
193 const struct spwd *src,
194 char *buffer, size_t buflen,
195 int *errnop) {
196
47fd7fa6
MC
197 assert(dest);
198 assert(src);
199 assert(src->sp_namp);
200 assert(src->sp_pwdp);
201
8a7adccb
ZJS
202 size_t required =
203 strlen(src->sp_namp) + 1 +
204 strlen(src->sp_pwdp) + 1;
47fd7fa6
MC
205
206 if (buflen < required) {
207 *errnop = ERANGE;
208 return NSS_STATUS_TRYAGAIN;
209 }
210
211 assert(buffer);
212
213 *dest = *src;
214
215 /* String fields point into the user-provided buffer */
216 dest->sp_namp = buffer;
217 dest->sp_pwdp = stpcpy(dest->sp_namp, src->sp_namp) + 1;
218 strcpy(dest->sp_pwdp, src->sp_pwdp);
219
220 return NSS_STATUS_SUCCESS;
221}
222
223static enum nss_status copy_synthesized_group(
224 struct group *dest,
225 const struct group *src,
226 char *buffer, size_t buflen,
227 int *errnop) {
228
47fd7fa6
MC
229 assert(dest);
230 assert(src);
231 assert(src->gr_name);
232 assert(src->gr_passwd);
233 assert(src->gr_mem);
234 assert(!*src->gr_mem); /* Our synthesized records' gr_mem is always just NULL... */
235
8a7adccb
ZJS
236 size_t required =
237 strlen(src->gr_name) + 1 +
238 strlen(src->gr_passwd) + 1 +
239 sizeof(char*); /* ...but that NULL still needs to be stored into the buffer! */
47fd7fa6 240
420a35c1 241 if (buflen < ALIGN(required)) {
47fd7fa6
MC
242 *errnop = ERANGE;
243 return NSS_STATUS_TRYAGAIN;
244 }
245
246 assert(buffer);
247
248 *dest = *src;
249
250 /* String fields point into the user-provided buffer */
251 dest->gr_name = buffer;
252 dest->gr_passwd = stpcpy(dest->gr_name, src->gr_name) + 1;
420a35c1 253 dest->gr_mem = ALIGN_PTR(stpcpy(dest->gr_passwd, src->gr_passwd) + 1);
47fd7fa6
MC
254 *dest->gr_mem = NULL;
255
256 return NSS_STATUS_SUCCESS;
257}
258
259static enum nss_status copy_synthesized_sgrp(
260 struct sgrp *dest,
261 const struct sgrp *src,
262 char *buffer, size_t buflen,
263 int *errnop) {
264
47fd7fa6
MC
265 assert(dest);
266 assert(src);
267 assert(src->sg_namp);
268 assert(src->sg_passwd);
269
8a7adccb
ZJS
270 size_t required =
271 strlen(src->sg_namp) + 1 +
272 strlen(src->sg_passwd) + 1;
47fd7fa6
MC
273
274 if (buflen < required) {
275 *errnop = ERANGE;
276 return NSS_STATUS_TRYAGAIN;
277 }
278
279 assert(buffer);
280
281 *dest = *src;
282
283 /* String fields point into the user-provided buffer */
284 dest->sg_namp = buffer;
285 dest->sg_passwd = stpcpy(dest->sg_namp, src->sg_namp) + 1;
286 strcpy(dest->sg_passwd, src->sg_passwd);
287
288 return NSS_STATUS_SUCCESS;
289}
290
409093fe
LP
291enum nss_status _nss_systemd_getpwnam_r(
292 const char *name,
293 struct passwd *pwd,
294 char *buffer, size_t buflen,
295 int *errnop) {
296
1684c56f
LP
297 enum nss_status status;
298 int e;
409093fe 299
06202b9e 300 PROTECT_ERRNO;
3339381f 301 NSS_ENTRYPOINT_BEGIN;
409093fe
LP
302
303 assert(name);
304 assert(pwd);
1684c56f 305 assert(errnop);
409093fe 306
1684c56f
LP
307 /* If the username is not valid, then we don't know it. Ideally libc would filter these for us
308 * anyway. We don't generate EINVAL here, because it isn't really out business to complain about
309 * invalid user names. */
7a8867ab 310 if (!valid_user_group_name(name, VALID_USER_RELAX))
06202b9e 311 return NSS_STATUS_NOTFOUND;
2129011e
LP
312
313 /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
efb9b3ba 314 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
1684c56f 315
47fd7fa6 316 if (streq(name, root_passwd.pw_name))
8a7adccb
ZJS
317 return copy_synthesized_passwd(pwd, &root_passwd,
318 default_root_shell(NULL),
319 buffer, buflen, errnop);
409093fe 320
1684c56f
LP
321 if (streq(name, nobody_passwd.pw_name)) {
322 if (!synthesize_nobody())
06202b9e 323 return NSS_STATUS_NOTFOUND;
fd63e712 324
8a7adccb
ZJS
325 return copy_synthesized_passwd(pwd, &nobody_passwd,
326 NULL,
327 buffer, buflen, errnop);
fd63e712
LP
328 }
329
1684c56f
LP
330 } else if (STR_IN_SET(name, root_passwd.pw_name, nobody_passwd.pw_name))
331 return NSS_STATUS_NOTFOUND;
409093fe 332
1684c56f
LP
333 status = userdb_getpwnam(name, pwd, buffer, buflen, &e);
334 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
cdccd29f 335 UNPROTECT_ERRNO;
1684c56f
LP
336 *errnop = e;
337 return status;
409093fe
LP
338 }
339
1684c56f 340 return status;
409093fe
LP
341}
342
343enum nss_status _nss_systemd_getpwuid_r(
344 uid_t uid,
345 struct passwd *pwd,
346 char *buffer, size_t buflen,
347 int *errnop) {
348
1684c56f
LP
349 enum nss_status status;
350 int e;
409093fe 351
06202b9e 352 PROTECT_ERRNO;
3339381f 353 NSS_ENTRYPOINT_BEGIN;
409093fe 354
1684c56f
LP
355 assert(pwd);
356 assert(errnop);
357
d6c575e3 358 if (!uid_is_valid(uid))
06202b9e 359 return NSS_STATUS_NOTFOUND;
409093fe 360
2129011e 361 /* Synthesize data for the root user and for nobody in case they are missing from /etc/passwd */
efb9b3ba 362 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
1684c56f 363
47fd7fa6 364 if (uid == root_passwd.pw_uid)
8a7adccb
ZJS
365 return copy_synthesized_passwd(pwd, &root_passwd,
366 default_root_shell(NULL),
367 buffer, buflen, errnop);
2129011e 368
1684c56f
LP
369 if (uid == nobody_passwd.pw_uid) {
370 if (!synthesize_nobody())
06202b9e 371 return NSS_STATUS_NOTFOUND;
fd63e712 372
8a7adccb
ZJS
373 return copy_synthesized_passwd(pwd, &nobody_passwd,
374 NULL,
375 buffer, buflen, errnop);
fd63e712
LP
376 }
377
1684c56f
LP
378 } else if (uid == root_passwd.pw_uid || uid == nobody_passwd.pw_uid)
379 return NSS_STATUS_NOTFOUND;
409093fe 380
1684c56f
LP
381 status = userdb_getpwuid(uid, pwd, buffer, buflen, &e);
382 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
cdccd29f 383 UNPROTECT_ERRNO;
1684c56f
LP
384 *errnop = e;
385 return status;
409093fe
LP
386 }
387
1684c56f 388 return status;
409093fe
LP
389}
390
f43a19ec
LP
391enum nss_status _nss_systemd_getspnam_r(
392 const char *name,
393 struct spwd *spwd,
394 char *buffer, size_t buflen,
395 int *errnop) {
396
397 enum nss_status status;
398 int e;
399
400 PROTECT_ERRNO;
401 NSS_ENTRYPOINT_BEGIN;
402
403 assert(name);
404 assert(spwd);
405 assert(errnop);
406
407 if (!valid_user_group_name(name, VALID_USER_RELAX))
408 return NSS_STATUS_NOTFOUND;
409
410 /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
efb9b3ba 411 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
f43a19ec 412
47fd7fa6
MC
413 if (streq(name, root_spwd.sp_namp))
414 return copy_synthesized_spwd(spwd, &root_spwd, buffer, buflen, errnop);
f43a19ec
LP
415
416 if (streq(name, nobody_spwd.sp_namp)) {
417 if (!synthesize_nobody())
418 return NSS_STATUS_NOTFOUND;
419
47fd7fa6 420 return copy_synthesized_spwd(spwd, &nobody_spwd, buffer, buflen, errnop);
f43a19ec
LP
421 }
422
423 } else if (STR_IN_SET(name, root_spwd.sp_namp, nobody_spwd.sp_namp))
424 return NSS_STATUS_NOTFOUND;
425
426 status = userdb_getspnam(name, spwd, buffer, buflen, &e);
427 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
428 UNPROTECT_ERRNO;
429 *errnop = e;
430 return status;
431 }
432
433 return status;
434}
435
3c3d384a
ZJS
436#pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
437
409093fe
LP
438enum nss_status _nss_systemd_getgrnam_r(
439 const char *name,
440 struct group *gr,
441 char *buffer, size_t buflen,
442 int *errnop) {
443
1684c56f
LP
444 enum nss_status status;
445 int e;
409093fe 446
06202b9e 447 PROTECT_ERRNO;
3339381f 448 NSS_ENTRYPOINT_BEGIN;
409093fe
LP
449
450 assert(name);
451 assert(gr);
1684c56f 452 assert(errnop);
409093fe 453
7a8867ab 454 if (!valid_user_group_name(name, VALID_USER_RELAX))
06202b9e 455 return NSS_STATUS_NOTFOUND;
2129011e 456
3e93027b 457 /* Synthesize records for root and nobody, in case they are missing from /etc/group */
efb9b3ba 458 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
1684c56f 459
47fd7fa6
MC
460 if (streq(name, root_group.gr_name))
461 return copy_synthesized_group(gr, &root_group, buffer, buflen, errnop);
409093fe 462
1684c56f
LP
463 if (streq(name, nobody_group.gr_name)) {
464 if (!synthesize_nobody())
06202b9e 465 return NSS_STATUS_NOTFOUND;
fd63e712 466
47fd7fa6 467 return copy_synthesized_group(gr, &nobody_group, buffer, buflen, errnop);
fd63e712
LP
468 }
469
1684c56f
LP
470 } else if (STR_IN_SET(name, root_group.gr_name, nobody_group.gr_name))
471 return NSS_STATUS_NOTFOUND;
409093fe 472
1684c56f
LP
473 status = userdb_getgrnam(name, gr, buffer, buflen, &e);
474 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
cdccd29f 475 UNPROTECT_ERRNO;
1684c56f
LP
476 *errnop = e;
477 return status;
409093fe
LP
478 }
479
1684c56f 480 return status;
409093fe
LP
481}
482
483enum nss_status _nss_systemd_getgrgid_r(
484 gid_t gid,
485 struct group *gr,
486 char *buffer, size_t buflen,
487 int *errnop) {
488
1684c56f
LP
489 enum nss_status status;
490 int e;
409093fe 491
06202b9e 492 PROTECT_ERRNO;
3339381f 493 NSS_ENTRYPOINT_BEGIN;
409093fe 494
1684c56f
LP
495 assert(gr);
496 assert(errnop);
497
d6c575e3 498 if (!gid_is_valid(gid))
06202b9e 499 return NSS_STATUS_NOTFOUND;
409093fe 500
2129011e 501 /* Synthesize records for root and nobody, in case they are missing from /etc/group */
efb9b3ba 502 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
1684c56f 503
47fd7fa6
MC
504 if (gid == root_group.gr_gid)
505 return copy_synthesized_group(gr, &root_group, buffer, buflen, errnop);
409093fe 506
1684c56f
LP
507 if (gid == nobody_group.gr_gid) {
508 if (!synthesize_nobody())
06202b9e 509 return NSS_STATUS_NOTFOUND;
fd63e712 510
47fd7fa6 511 return copy_synthesized_group(gr, &nobody_group, buffer, buflen, errnop);
fd63e712
LP
512 }
513
1684c56f
LP
514 } else if (gid == root_group.gr_gid || gid == nobody_group.gr_gid)
515 return NSS_STATUS_NOTFOUND;
409093fe 516
1684c56f
LP
517 status = userdb_getgrgid(gid, gr, buffer, buflen, &e);
518 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
cdccd29f 519 UNPROTECT_ERRNO;
1684c56f
LP
520 *errnop = e;
521 return status;
409093fe
LP
522 }
523
1684c56f 524 return status;
12c2c56d
YW
525}
526
f43a19ec
LP
527enum nss_status _nss_systemd_getsgnam_r(
528 const char *name,
529 struct sgrp *sgrp,
530 char *buffer, size_t buflen,
531 int *errnop) {
532
533 enum nss_status status;
534 int e;
535
536 PROTECT_ERRNO;
537 NSS_ENTRYPOINT_BEGIN;
538
539 assert(name);
540 assert(sgrp);
541 assert(errnop);
542
543 if (!valid_user_group_name(name, VALID_USER_RELAX))
544 return NSS_STATUS_NOTFOUND;
545
546 /* Synthesize records for root and nobody, in case they are missing from /etc/group */
efb9b3ba 547 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
f43a19ec 548
47fd7fa6
MC
549 if (streq(name, root_sgrp.sg_namp))
550 return copy_synthesized_sgrp(sgrp, &root_sgrp, buffer, buflen, errnop);
f43a19ec
LP
551
552 if (streq(name, nobody_sgrp.sg_namp)) {
553 if (!synthesize_nobody())
554 return NSS_STATUS_NOTFOUND;
555
47fd7fa6 556 return copy_synthesized_sgrp(sgrp, &nobody_sgrp, buffer, buflen, errnop);
f43a19ec
LP
557 }
558
559 } else if (STR_IN_SET(name, root_sgrp.sg_namp, nobody_sgrp.sg_namp))
560 return NSS_STATUS_NOTFOUND;
561
562 status = userdb_getsgnam(name, sgrp, buffer, buflen, &e);
563 if (IN_SET(status, NSS_STATUS_UNAVAIL, NSS_STATUS_TRYAGAIN)) {
564 UNPROTECT_ERRNO;
565 *errnop = e;
566 return status;
567 }
568
569 return status;
570}
571
12c2c56d 572static enum nss_status nss_systemd_endent(GetentData *p) {
06202b9e 573 PROTECT_ERRNO;
3339381f 574 NSS_ENTRYPOINT_BEGIN;
12c2c56d 575
1684c56f
LP
576 assert(p);
577
2d882d35
LP
578 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&p->mutex);
579 (void) _l; /* make llvm shut up about _l not being used. */
37bc9dcc 580
1684c56f
LP
581 p->iterator = userdb_iterator_free(p->iterator);
582 p->by_membership = false;
12c2c56d
YW
583
584 return NSS_STATUS_SUCCESS;
585}
586
587enum nss_status _nss_systemd_endpwent(void) {
588 return nss_systemd_endent(&getpwent_data);
589}
590
f43a19ec
LP
591enum nss_status _nss_systemd_endspent(void) {
592 return nss_systemd_endent(&getspent_data);
593}
594
12c2c56d
YW
595enum nss_status _nss_systemd_endgrent(void) {
596 return nss_systemd_endent(&getgrent_data);
597}
598
f43a19ec
LP
599enum nss_status _nss_systemd_endsgent(void) {
600 return nss_systemd_endent(&getsgent_data);
601}
602
1684c56f 603enum nss_status _nss_systemd_setpwent(int stayopen) {
2d882d35
LP
604 int r;
605
1684c56f 606 PROTECT_ERRNO;
3339381f 607 NSS_ENTRYPOINT_BEGIN;
12c2c56d 608
037b0a47 609 if (_nss_systemd_is_blocked())
1684c56f 610 return NSS_STATUS_NOTFOUND;
12c2c56d 611
2d882d35
LP
612 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getpwent_data.mutex);
613 (void) _l; /* make llvm shut up about _l not being used. */
12c2c56d 614
1684c56f
LP
615 getpwent_data.iterator = userdb_iterator_free(getpwent_data.iterator);
616 getpwent_data.by_membership = false;
12c2c56d 617
9494da41
LP
618 /* Don't synthesize root/nobody when iterating. Let nss-files take care of that. If the two records
619 * are missing there, then that's fine, after all getpwent() is known to be possibly incomplete
620 * (think: LDAP/NIS type situations), and our synthesizing of root/nobody is a robustness fallback
621 * only, which matters for getpwnam()/getpwuid() primarily, which are the main NSS entrypoints to the
622 * user database. */
74192916 623 r = userdb_all(/* match= */ NULL, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getpwent_data.iterator);
37bc9dcc 624 return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
1684c56f 625}
12c2c56d 626
1684c56f 627enum nss_status _nss_systemd_setgrent(int stayopen) {
2d882d35
LP
628 int r;
629
1684c56f 630 PROTECT_ERRNO;
3339381f 631 NSS_ENTRYPOINT_BEGIN;
12c2c56d 632
037b0a47 633 if (_nss_systemd_is_blocked())
1684c56f 634 return NSS_STATUS_NOTFOUND;
12c2c56d 635
2d882d35
LP
636 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getgrent_data.mutex);
637 (void) _l; /* make llvm shut up about _l not being used. */
1684c56f
LP
638
639 getgrent_data.iterator = userdb_iterator_free(getgrent_data.iterator);
1fdfca4d 640 getgrent_data.by_membership = false;
1684c56f 641
44eb6b81 642 /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE_INTRINSIC here */
74192916 643 r = groupdb_all(/* match= */ NULL, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getgrent_data.iterator);
37bc9dcc 644 return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
12c2c56d
YW
645}
646
f43a19ec
LP
647enum nss_status _nss_systemd_setspent(int stayopen) {
648 int r;
649
650 PROTECT_ERRNO;
651 NSS_ENTRYPOINT_BEGIN;
652
653 if (_nss_systemd_is_blocked())
654 return NSS_STATUS_NOTFOUND;
655
656 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getspent_data.mutex);
657 (void) _l; /* make llvm shut up about _l not being used. */
658
659 getspent_data.iterator = userdb_iterator_free(getspent_data.iterator);
660 getspent_data.by_membership = false;
661
44eb6b81 662 /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE_INTRINSIC here */
74192916 663 r = userdb_all(/* match= */ NULL, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getspent_data.iterator);
f43a19ec
LP
664 return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
665}
666
667enum nss_status _nss_systemd_setsgent(int stayopen) {
668 int r;
669
670 PROTECT_ERRNO;
671 NSS_ENTRYPOINT_BEGIN;
672
673 if (_nss_systemd_is_blocked())
674 return NSS_STATUS_NOTFOUND;
675
676 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getsgent_data.mutex);
677 (void) _l; /* make llvm shut up about _l not being used. */
678
679 getsgent_data.iterator = userdb_iterator_free(getsgent_data.iterator);
680 getsgent_data.by_membership = false;
681
682 /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
74192916 683 r = groupdb_all(/* match= */ NULL, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC | USERDB_DONT_SYNTHESIZE_FOREIGN, &getsgent_data.iterator);
f43a19ec
LP
684 return r < 0 ? NSS_STATUS_UNAVAIL : NSS_STATUS_SUCCESS;
685}
686
1684c56f
LP
687enum nss_status _nss_systemd_getpwent_r(
688 struct passwd *result,
689 char *buffer, size_t buflen,
690 int *errnop) {
691
692 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
1684c56f 693 int r;
12c2c56d 694
06202b9e 695 PROTECT_ERRNO;
3339381f 696 NSS_ENTRYPOINT_BEGIN;
12c2c56d 697
1684c56f
LP
698 assert(result);
699 assert(errnop);
12c2c56d 700
037b0a47 701 if (_nss_systemd_is_blocked())
1684c56f 702 return NSS_STATUS_NOTFOUND;
12c2c56d 703
2d882d35
LP
704 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getpwent_data.mutex);
705 (void) _l; /* make llvm shut up about _l not being used. */
12c2c56d 706
1684c56f
LP
707 if (!getpwent_data.iterator) {
708 UNPROTECT_ERRNO;
709 *errnop = EHOSTDOWN;
37bc9dcc 710 return NSS_STATUS_UNAVAIL;
12c2c56d
YW
711 }
712
74192916 713 r = userdb_iterator_get(getpwent_data.iterator, /* match= */ NULL, &ur);
37bc9dcc
ZJS
714 if (r == -ESRCH)
715 return NSS_STATUS_NOTFOUND;
1684c56f
LP
716 if (r < 0) {
717 UNPROTECT_ERRNO;
718 *errnop = -r;
37bc9dcc 719 return NSS_STATUS_UNAVAIL;
12c2c56d
YW
720 }
721
1684c56f
LP
722 r = nss_pack_user_record(ur, result, buffer, buflen);
723 if (r < 0) {
724 UNPROTECT_ERRNO;
725 *errnop = -r;
37bc9dcc 726 return NSS_STATUS_TRYAGAIN;
12c2c56d 727 }
12c2c56d 728
37bc9dcc 729 return NSS_STATUS_SUCCESS;
12c2c56d
YW
730}
731
1684c56f
LP
732enum nss_status _nss_systemd_getgrent_r(
733 struct group *result,
734 char *buffer, size_t buflen,
735 int *errnop) {
12c2c56d 736
1684c56f
LP
737 _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
738 _cleanup_free_ char **members = NULL;
1684c56f 739 int r;
12c2c56d 740
06202b9e 741 PROTECT_ERRNO;
3339381f 742 NSS_ENTRYPOINT_BEGIN;
12c2c56d
YW
743
744 assert(result);
12c2c56d
YW
745 assert(errnop);
746
037b0a47
LP
747 if (_nss_systemd_is_blocked())
748 return NSS_STATUS_NOTFOUND;
1684c56f 749
2d882d35
LP
750 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getgrent_data.mutex);
751 (void) _l; /* make llvm shut up about _l not being used. */
12c2c56d 752
1684c56f
LP
753 if (!getgrent_data.iterator) {
754 UNPROTECT_ERRNO;
755 *errnop = EHOSTDOWN;
37bc9dcc 756 return NSS_STATUS_UNAVAIL;
1684c56f
LP
757 }
758
759 if (!getgrent_data.by_membership) {
74192916 760 r = groupdb_iterator_get(getgrent_data.iterator, /* match= */ NULL, &gr);
1684c56f 761 if (r == -ESRCH) {
e7e9a9d0 762 /* So we finished iterating native groups now. Let's now continue with iterating
1684c56f
LP
763 * native memberships, and generate additional group entries for any groups
764 * referenced there that are defined in NSS only. This means for those groups there
765 * will be two or more entries generated during iteration, but this is apparently how
766 * this is supposed to work, and what other implementations do too. Clients are
767 * supposed to merge the group records found during iteration automatically. */
768 getgrent_data.iterator = userdb_iterator_free(getgrent_data.iterator);
769
770 r = membershipdb_all(nss_glue_userdb_flags(), &getgrent_data.iterator);
a1aa41e4 771 if (r < 0 && r != -ESRCH) {
1684c56f
LP
772 UNPROTECT_ERRNO;
773 *errnop = -r;
37bc9dcc 774 return NSS_STATUS_UNAVAIL;
1684c56f
LP
775 }
776
777 getgrent_data.by_membership = true;
778 } else if (r < 0) {
cdccd29f 779 UNPROTECT_ERRNO;
1684c56f 780 *errnop = -r;
37bc9dcc 781 return NSS_STATUS_UNAVAIL;
1684c56f
LP
782 } else if (!STR_IN_SET(gr->group_name, root_group.gr_name, nobody_group.gr_name)) {
783 r = membershipdb_by_group_strv(gr->group_name, nss_glue_userdb_flags(), &members);
a1aa41e4 784 if (r < 0 && r != -ESRCH) {
1684c56f
LP
785 UNPROTECT_ERRNO;
786 *errnop = -r;
37bc9dcc 787 return NSS_STATUS_UNAVAIL;
1684c56f 788 }
12c2c56d 789 }
1684c56f 790 }
12c2c56d 791
1684c56f 792 if (getgrent_data.by_membership) {
037b0a47 793 _cleanup_(_nss_systemd_unblockp) bool blocked = false;
1684c56f 794
a1aa41e4
LP
795 if (!getgrent_data.iterator)
796 return NSS_STATUS_NOTFOUND;
797
1684c56f
LP
798 for (;;) {
799 _cleanup_free_ char *user_name = NULL, *group_name = NULL;
800
801 r = membershipdb_iterator_get(getgrent_data.iterator, &user_name, &group_name);
37bc9dcc
ZJS
802 if (r == -ESRCH)
803 return NSS_STATUS_NOTFOUND;
1684c56f
LP
804 if (r < 0) {
805 UNPROTECT_ERRNO;
806 *errnop = -r;
37bc9dcc 807 return NSS_STATUS_UNAVAIL;
1684c56f
LP
808 }
809
810 if (STR_IN_SET(user_name, root_passwd.pw_name, nobody_passwd.pw_name))
811 continue;
812 if (STR_IN_SET(group_name, root_group.gr_name, nobody_group.gr_name))
813 continue;
814
815 /* We are about to recursively call into NSS, let's make sure we disable recursion into our own code. */
037b0a47
LP
816 if (!blocked) {
817 r = _nss_systemd_block(true);
818 if (r < 0) {
1684c56f 819 UNPROTECT_ERRNO;
037b0a47 820 *errnop = -r;
37bc9dcc 821 return NSS_STATUS_UNAVAIL;
1684c56f 822 }
037b0a47
LP
823
824 blocked = true;
1684c56f
LP
825 }
826
ed30170e 827 r = nss_group_record_by_name(group_name, false, &gr);
1684c56f
LP
828 if (r == -ESRCH)
829 continue;
830 if (r < 0) {
831 log_debug_errno(r, "Failed to do NSS check for group '%s', ignoring: %m", group_name);
832 continue;
833 }
834
835 members = strv_new(user_name);
836 if (!members) {
837 UNPROTECT_ERRNO;
838 *errnop = ENOMEM;
37bc9dcc 839 return NSS_STATUS_TRYAGAIN;
1684c56f
LP
840 }
841
842 /* Note that we currently generate one group entry per user that is part of a
843 * group. It's a bit ugly, but equivalent to generating a single entry with a set of
844 * members in them. */
845 break;
846 }
12c2c56d 847 }
1684c56f
LP
848
849 r = nss_pack_group_record(gr, members, result, buffer, buflen);
850 if (r < 0) {
851 UNPROTECT_ERRNO;
852 *errnop = -r;
37bc9dcc 853 return NSS_STATUS_TRYAGAIN;
12c2c56d
YW
854 }
855
37bc9dcc 856 return NSS_STATUS_SUCCESS;
12c2c56d
YW
857}
858
f43a19ec
LP
859enum nss_status _nss_systemd_getspent_r(
860 struct spwd *result,
861 char *buffer, size_t buflen,
862 int *errnop) {
863
864 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
865 int r;
866
867 PROTECT_ERRNO;
868 NSS_ENTRYPOINT_BEGIN;
869
870 assert(result);
871 assert(errnop);
872
873 if (_nss_systemd_is_blocked())
874 return NSS_STATUS_NOTFOUND;
875
876 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getspent_data.mutex);
877 (void) _l; /* make llvm shut up about _l not being used. */
878
879 if (!getspent_data.iterator) {
880 UNPROTECT_ERRNO;
881 *errnop = EHOSTDOWN;
882 return NSS_STATUS_UNAVAIL;
883 }
884
885 for (;;) {
74192916 886 r = userdb_iterator_get(getspent_data.iterator, /* match= */ NULL, &ur);
f43a19ec
LP
887 if (r == -ESRCH)
888 return NSS_STATUS_NOTFOUND;
889 if (r < 0) {
890 UNPROTECT_ERRNO;
891 *errnop = -r;
892 return NSS_STATUS_UNAVAIL;
893 }
894
895 if (!ur->incomplete) /* don't synthesize shadow records for records where we couldn't read shadow data */
896 break;
897
898 ur = user_record_unref(ur);
899 }
900
901 r = nss_pack_user_record_shadow(ur, result, buffer, buflen);
902 if (r < 0) {
903 UNPROTECT_ERRNO;
904 *errnop = -r;
905 return NSS_STATUS_TRYAGAIN;
906 }
907
908 return NSS_STATUS_SUCCESS;
909}
910
911enum nss_status _nss_systemd_getsgent_r(
912 struct sgrp *result,
913 char *buffer, size_t buflen,
914 int *errnop) {
915
916 _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
917 int r;
918
919 PROTECT_ERRNO;
920 NSS_ENTRYPOINT_BEGIN;
921
922 assert(result);
923 assert(errnop);
924
925 if (_nss_systemd_is_blocked())
926 return NSS_STATUS_NOTFOUND;
927
928 _cleanup_(pthread_mutex_unlock_assertp) pthread_mutex_t *_l = pthread_mutex_lock_assert(&getsgent_data.mutex);
929 (void) _l; /* make llvm shut up about _l not being used. */
930
931 if (!getsgent_data.iterator) {
932 UNPROTECT_ERRNO;
933 *errnop = EHOSTDOWN;
934 return NSS_STATUS_UNAVAIL;
935 }
936
937 for (;;) {
74192916 938 r = groupdb_iterator_get(getsgent_data.iterator, /* match= */ NULL, &gr);
f43a19ec
LP
939 if (r == -ESRCH)
940 return NSS_STATUS_NOTFOUND;
941 if (r < 0) {
942 UNPROTECT_ERRNO;
943 *errnop = -r;
944 return NSS_STATUS_UNAVAIL;
945 }
946
947 if (!gr->incomplete) /* don't synthesize shadow records for records where we couldn't read shadow data */
948 break;
949
950 gr = group_record_unref(gr);
951 }
952
953 r = nss_pack_group_record_shadow(gr, result, buffer, buflen);
954 if (r < 0) {
955 UNPROTECT_ERRNO;
956 *errnop = -r;
957 return NSS_STATUS_TRYAGAIN;
958 }
959
960 return NSS_STATUS_SUCCESS;
961}
962
1684c56f
LP
963enum nss_status _nss_systemd_initgroups_dyn(
964 const char *user_name,
965 gid_t gid,
966 long *start,
967 long *size,
968 gid_t **groupsp,
9ec7d7ae 969 long limit,
1684c56f
LP
970 int *errnop) {
971
972 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
973 bool any = false;
974 int r;
12c2c56d 975
06202b9e 976 PROTECT_ERRNO;
3339381f 977 NSS_ENTRYPOINT_BEGIN;
12c2c56d 978
1684c56f
LP
979 assert(user_name);
980 assert(start);
981 assert(size);
982 assert(groupsp);
12c2c56d
YW
983 assert(errnop);
984
7a8867ab 985 if (!valid_user_group_name(user_name, VALID_USER_RELAX))
1684c56f
LP
986 return NSS_STATUS_NOTFOUND;
987
988 /* Don't allow extending these two special users, the same as we won't resolve them via getpwnam() */
989 if (STR_IN_SET(user_name, root_passwd.pw_name, nobody_passwd.pw_name))
990 return NSS_STATUS_NOTFOUND;
991
037b0a47 992 if (_nss_systemd_is_blocked())
1684c56f
LP
993 return NSS_STATUS_NOTFOUND;
994
995 r = membershipdb_by_user(user_name, nss_glue_userdb_flags(), &iterator);
996 if (r < 0) {
997 UNPROTECT_ERRNO;
998 *errnop = -r;
999 return NSS_STATUS_UNAVAIL;
1000 }
12c2c56d 1001
1684c56f
LP
1002 for (;;) {
1003 _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
1004 _cleanup_free_ char *group_name = NULL;
1005
1006 r = membershipdb_iterator_get(iterator, NULL, &group_name);
1007 if (r == -ESRCH)
1008 break;
1009 if (r < 0) {
cdccd29f 1010 UNPROTECT_ERRNO;
1684c56f
LP
1011 *errnop = -r;
1012 return NSS_STATUS_UNAVAIL;
12c2c56d
YW
1013 }
1014
1684c56f
LP
1015 /* The group might be defined via traditional NSS only, hence let's do a full look-up without
1016 * disabling NSS. This means we are operating recursively here. */
12c2c56d 1017
74192916 1018 r = groupdb_by_name(group_name, /* match= */ NULL, (nss_glue_userdb_flags() & ~USERDB_EXCLUDE_NSS) | USERDB_SUPPRESS_SHADOW, &g);
1684c56f
LP
1019 if (r == -ESRCH)
1020 continue;
1021 if (r < 0) {
1022 log_debug_errno(r, "Failed to resolve group '%s', ignoring: %m", group_name);
1023 continue;
1024 }
12c2c56d 1025
1684c56f
LP
1026 if (g->gid == gid)
1027 continue;
12c2c56d 1028
1684c56f
LP
1029 if (*start >= *size) {
1030 gid_t *new_groups;
1031 long new_size;
1032
1033 if (limit > 0 && *size >= limit) /* Reached the limit.? */
1034 break;
1035
1036 if (*size > LONG_MAX/2) { /* Check for overflow */
1037 UNPROTECT_ERRNO;
1038 *errnop = ENOMEM;
1039 return NSS_STATUS_TRYAGAIN;
1040 }
1041
1042 new_size = *start * 2;
1043 if (limit > 0 && new_size > limit)
1044 new_size = limit;
1045
1046 /* Enlarge buffer */
d7a0f1f4 1047 new_groups = reallocarray(*groupsp, new_size, sizeof(**groupsp));
1684c56f
LP
1048 if (!new_groups) {
1049 UNPROTECT_ERRNO;
1050 *errnop = ENOMEM;
1051 return NSS_STATUS_TRYAGAIN;
1052 }
1053
1054 *groupsp = new_groups;
1055 *size = new_size;
1056 }
12c2c56d 1057
1684c56f
LP
1058 (*groupsp)[(*start)++] = g->gid;
1059 any = true;
1060 }
12c2c56d 1061
1684c56f 1062 return any ? NSS_STATUS_SUCCESS : NSS_STATUS_NOTFOUND;
12c2c56d 1063}
037b0a47
LP
1064
1065static thread_local unsigned _blocked = 0;
1066
1067_public_ int _nss_systemd_block(bool b) {
1068
1069 /* This blocks recursively: it's blocked for as many times this function is called with `true` until
1070 * it is called an equal time with `false`. */
1071
1072 if (b) {
1073 if (_blocked >= UINT_MAX)
1074 return -EOVERFLOW;
1075
1076 _blocked++;
1077 } else {
1078 if (_blocked <= 0)
1079 return -EOVERFLOW;
1080
1081 _blocked--;
1082 }
1083
1084 return b; /* Return what is passed in, i.e. the new state from the PoV of the caller */
1085}
1086
1087_public_ bool _nss_systemd_is_blocked(void) {
1088 return _blocked > 0;
1089}