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