1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #include "alloc-util.h"
11 #include "errno-util.h"
13 #include "nss-systemd.h"
15 #include "pthread-util.h"
16 #include "signal-util.h"
17 #include "string-util.h"
19 #include "user-record-nss.h"
20 #include "user-util.h"
22 #include "userdb-glue.h"
24 static const struct passwd root_passwd
= {
25 .pw_name
= (char*) "root",
26 .pw_passwd
= (char*) PASSWORD_SEE_SHADOW
,
29 .pw_gecos
= (char*) "Super User",
30 .pw_dir
= (char*) "/root",
34 static const struct spwd root_spwd
= {
35 .sp_namp
= (char*) "root",
36 .sp_pwdp
= (char*) PASSWORD_LOCKED_AND_INVALID
,
43 .sp_flag
= ULONG_MAX
, /* this appears to be what everybody does ... */
46 static const struct passwd nobody_passwd
= {
47 .pw_name
= (char*) NOBODY_USER_NAME
,
48 .pw_passwd
= (char*) PASSWORD_LOCKED_AND_INVALID
,
51 .pw_gecos
= (char*) "Kernel Overflow User",
52 .pw_dir
= (char*) "/",
53 .pw_shell
= (char*) NOLOGIN
,
56 static const struct spwd nobody_spwd
= {
57 .sp_namp
= (char*) NOBODY_USER_NAME
,
58 .sp_pwdp
= (char*) PASSWORD_LOCKED_AND_INVALID
,
65 .sp_flag
= ULONG_MAX
, /* this appears to be what everybody does ... */
68 static const struct group root_group
= {
69 .gr_name
= (char*) "root",
71 .gr_passwd
= (char*) PASSWORD_SEE_SHADOW
,
72 .gr_mem
= (char*[]) { NULL
},
75 static const struct sgrp root_sgrp
= {
76 .sg_namp
= (char*) "root",
77 .sg_passwd
= (char*) PASSWORD_LOCKED_AND_INVALID
,
80 static const struct group nobody_group
= {
81 .gr_name
= (char*) NOBODY_GROUP_NAME
,
83 .gr_passwd
= (char*) PASSWORD_LOCKED_AND_INVALID
,
84 .gr_mem
= (char*[]) { NULL
},
87 static const struct sgrp nobody_sgrp
= {
88 .sg_namp
= (char*) NOBODY_GROUP_NAME
,
89 .sg_passwd
= (char*) PASSWORD_LOCKED_AND_INVALID
,
92 typedef 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
;
100 /* Applies to group iterations only: true while we iterate over groups defined through NSS, false
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. */
107 DISABLE_WARNING_ZERO_AS_NULL_POINTER_CONSTANT
;
108 static GetentData getpwent_data
= {
109 .mutex
= PTHREAD_MUTEX_INITIALIZER
,
112 static GetentData getgrent_data
= {
113 .mutex
= PTHREAD_MUTEX_INITIALIZER
,
116 static GetentData getspent_data
= {
117 .mutex
= PTHREAD_MUTEX_INITIALIZER
,
120 static GetentData getsgent_data
= {
121 .mutex
= PTHREAD_MUTEX_INITIALIZER
,
125 static void setup_logging_once(void) {
126 static pthread_once_t once
= PTHREAD_ONCE_INIT
;
127 assert_se(pthread_once(&once
, log_parse_environment_variables
) == 0);
130 #define NSS_ENTRYPOINT_BEGIN \
131 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); \
134 NSS_GETPW_PROTOTYPES(systemd
);
135 NSS_GETSP_PROTOTYPES(systemd
);
136 NSS_GETGR_PROTOTYPES(systemd
);
137 NSS_GETSG_PROTOTYPES(systemd
);
138 NSS_PWENT_PROTOTYPES(systemd
);
139 NSS_SPENT_PROTOTYPES(systemd
);
140 NSS_GRENT_PROTOTYPES(systemd
);
141 NSS_SGENT_PROTOTYPES(systemd
);
142 NSS_INITGROUPS_PROTOTYPE(systemd
);
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. */
148 static enum nss_status
copy_synthesized_passwd(
150 const struct passwd
*src
,
151 const char *fallback_shell
,
152 char *buffer
, size_t buflen
,
157 assert(src
->pw_name
);
158 assert(src
->pw_passwd
);
159 assert(src
->pw_gecos
);
162 const char *shell
= ASSERT_PTR(src
->pw_shell
?: fallback_shell
);
165 strlen(src
->pw_name
) + 1 +
166 strlen(src
->pw_passwd
) + 1 +
167 strlen(src
->pw_gecos
) + 1 +
168 strlen(src
->pw_dir
) + 1 +
171 if (buflen
< required
) {
173 return NSS_STATUS_TRYAGAIN
;
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;
186 strcpy(dest
->pw_shell
, shell
);
188 return NSS_STATUS_SUCCESS
;
191 static enum nss_status
copy_synthesized_spwd(
193 const struct spwd
*src
,
194 char *buffer
, size_t buflen
,
199 assert(src
->sp_namp
);
200 assert(src
->sp_pwdp
);
203 strlen(src
->sp_namp
) + 1 +
204 strlen(src
->sp_pwdp
) + 1;
206 if (buflen
< required
) {
208 return NSS_STATUS_TRYAGAIN
;
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
);
220 return NSS_STATUS_SUCCESS
;
223 static enum nss_status
copy_synthesized_group(
225 const struct group
*src
,
226 char *buffer
, size_t buflen
,
231 assert(src
->gr_name
);
232 assert(src
->gr_passwd
);
234 assert(!*src
->gr_mem
); /* Our synthesized records' gr_mem is always just NULL... */
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! */
241 if (buflen
< ALIGN(required
)) {
243 return NSS_STATUS_TRYAGAIN
;
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;
253 dest
->gr_mem
= ALIGN_PTR(stpcpy(dest
->gr_passwd
, src
->gr_passwd
) + 1);
254 *dest
->gr_mem
= NULL
;
256 return NSS_STATUS_SUCCESS
;
259 static enum nss_status
copy_synthesized_sgrp(
261 const struct sgrp
*src
,
262 char *buffer
, size_t buflen
,
267 assert(src
->sg_namp
);
268 assert(src
->sg_passwd
);
271 strlen(src
->sg_namp
) + 1 +
272 strlen(src
->sg_passwd
) + 1;
274 if (buflen
< required
) {
276 return NSS_STATUS_TRYAGAIN
;
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
);
288 return NSS_STATUS_SUCCESS
;
291 enum nss_status
_nss_systemd_getpwnam_r(
294 char *buffer
, size_t buflen
,
297 enum nss_status status
;
301 NSS_ENTRYPOINT_BEGIN
;
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. */
310 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
311 return NSS_STATUS_NOTFOUND
;
313 /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
314 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
316 if (streq(name
, root_passwd
.pw_name
))
317 return copy_synthesized_passwd(pwd
, &root_passwd
,
318 default_root_shell(NULL
),
319 buffer
, buflen
, errnop
);
321 if (streq(name
, nobody_passwd
.pw_name
)) {
322 if (!synthesize_nobody())
323 return NSS_STATUS_NOTFOUND
;
325 return copy_synthesized_passwd(pwd
, &nobody_passwd
,
327 buffer
, buflen
, errnop
);
330 } else if (STR_IN_SET(name
, root_passwd
.pw_name
, nobody_passwd
.pw_name
))
331 return NSS_STATUS_NOTFOUND
;
333 status
= userdb_getpwnam(name
, pwd
, buffer
, buflen
, &e
);
334 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
343 enum nss_status
_nss_systemd_getpwuid_r(
346 char *buffer
, size_t buflen
,
349 enum nss_status status
;
353 NSS_ENTRYPOINT_BEGIN
;
358 if (!uid_is_valid(uid
))
359 return NSS_STATUS_NOTFOUND
;
361 /* Synthesize data for the root user and for nobody in case they are missing from /etc/passwd */
362 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
364 if (uid
== root_passwd
.pw_uid
)
365 return copy_synthesized_passwd(pwd
, &root_passwd
,
366 default_root_shell(NULL
),
367 buffer
, buflen
, errnop
);
369 if (uid
== nobody_passwd
.pw_uid
) {
370 if (!synthesize_nobody())
371 return NSS_STATUS_NOTFOUND
;
373 return copy_synthesized_passwd(pwd
, &nobody_passwd
,
375 buffer
, buflen
, errnop
);
378 } else if (uid
== root_passwd
.pw_uid
|| uid
== nobody_passwd
.pw_uid
)
379 return NSS_STATUS_NOTFOUND
;
381 status
= userdb_getpwuid(uid
, pwd
, buffer
, buflen
, &e
);
382 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
391 enum nss_status
_nss_systemd_getspnam_r(
394 char *buffer
, size_t buflen
,
397 enum nss_status status
;
401 NSS_ENTRYPOINT_BEGIN
;
407 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
408 return NSS_STATUS_NOTFOUND
;
410 /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */
411 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
413 if (streq(name
, root_spwd
.sp_namp
))
414 return copy_synthesized_spwd(spwd
, &root_spwd
, buffer
, buflen
, errnop
);
416 if (streq(name
, nobody_spwd
.sp_namp
)) {
417 if (!synthesize_nobody())
418 return NSS_STATUS_NOTFOUND
;
420 return copy_synthesized_spwd(spwd
, &nobody_spwd
, buffer
, buflen
, errnop
);
423 } else if (STR_IN_SET(name
, root_spwd
.sp_namp
, nobody_spwd
.sp_namp
))
424 return NSS_STATUS_NOTFOUND
;
426 status
= userdb_getspnam(name
, spwd
, buffer
, buflen
, &e
);
427 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
436 #pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
438 enum nss_status
_nss_systemd_getgrnam_r(
441 char *buffer
, size_t buflen
,
444 enum nss_status status
;
448 NSS_ENTRYPOINT_BEGIN
;
454 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
455 return NSS_STATUS_NOTFOUND
;
457 /* Synthesize records for root and nobody, in case they are missing from /etc/group */
458 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
460 if (streq(name
, root_group
.gr_name
))
461 return copy_synthesized_group(gr
, &root_group
, buffer
, buflen
, errnop
);
463 if (streq(name
, nobody_group
.gr_name
)) {
464 if (!synthesize_nobody())
465 return NSS_STATUS_NOTFOUND
;
467 return copy_synthesized_group(gr
, &nobody_group
, buffer
, buflen
, errnop
);
470 } else if (STR_IN_SET(name
, root_group
.gr_name
, nobody_group
.gr_name
))
471 return NSS_STATUS_NOTFOUND
;
473 status
= userdb_getgrnam(name
, gr
, buffer
, buflen
, &e
);
474 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
483 enum nss_status
_nss_systemd_getgrgid_r(
486 char *buffer
, size_t buflen
,
489 enum nss_status status
;
493 NSS_ENTRYPOINT_BEGIN
;
498 if (!gid_is_valid(gid
))
499 return NSS_STATUS_NOTFOUND
;
501 /* Synthesize records for root and nobody, in case they are missing from /etc/group */
502 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
504 if (gid
== root_group
.gr_gid
)
505 return copy_synthesized_group(gr
, &root_group
, buffer
, buflen
, errnop
);
507 if (gid
== nobody_group
.gr_gid
) {
508 if (!synthesize_nobody())
509 return NSS_STATUS_NOTFOUND
;
511 return copy_synthesized_group(gr
, &nobody_group
, buffer
, buflen
, errnop
);
514 } else if (gid
== root_group
.gr_gid
|| gid
== nobody_group
.gr_gid
)
515 return NSS_STATUS_NOTFOUND
;
517 status
= userdb_getgrgid(gid
, gr
, buffer
, buflen
, &e
);
518 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
527 enum nss_status
_nss_systemd_getsgnam_r(
530 char *buffer
, size_t buflen
,
533 enum nss_status status
;
537 NSS_ENTRYPOINT_BEGIN
;
543 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
544 return NSS_STATUS_NOTFOUND
;
546 /* Synthesize records for root and nobody, in case they are missing from /etc/group */
547 if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) {
549 if (streq(name
, root_sgrp
.sg_namp
))
550 return copy_synthesized_sgrp(sgrp
, &root_sgrp
, buffer
, buflen
, errnop
);
552 if (streq(name
, nobody_sgrp
.sg_namp
)) {
553 if (!synthesize_nobody())
554 return NSS_STATUS_NOTFOUND
;
556 return copy_synthesized_sgrp(sgrp
, &nobody_sgrp
, buffer
, buflen
, errnop
);
559 } else if (STR_IN_SET(name
, root_sgrp
.sg_namp
, nobody_sgrp
.sg_namp
))
560 return NSS_STATUS_NOTFOUND
;
562 status
= userdb_getsgnam(name
, sgrp
, buffer
, buflen
, &e
);
563 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
572 static enum nss_status
nss_systemd_endent(GetentData
*p
) {
574 NSS_ENTRYPOINT_BEGIN
;
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. */
581 p
->iterator
= userdb_iterator_free(p
->iterator
);
582 p
->by_membership
= false;
584 return NSS_STATUS_SUCCESS
;
587 enum nss_status
_nss_systemd_endpwent(void) {
588 return nss_systemd_endent(&getpwent_data
);
591 enum nss_status
_nss_systemd_endspent(void) {
592 return nss_systemd_endent(&getspent_data
);
595 enum nss_status
_nss_systemd_endgrent(void) {
596 return nss_systemd_endent(&getgrent_data
);
599 enum nss_status
_nss_systemd_endsgent(void) {
600 return nss_systemd_endent(&getsgent_data
);
603 enum nss_status
_nss_systemd_setpwent(int stayopen
) {
607 NSS_ENTRYPOINT_BEGIN
;
609 if (_nss_systemd_is_blocked())
610 return NSS_STATUS_NOTFOUND
;
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. */
615 getpwent_data
.iterator
= userdb_iterator_free(getpwent_data
.iterator
);
616 getpwent_data
.by_membership
= false;
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
623 r
= userdb_all(/* match= */ NULL
, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC
| USERDB_DONT_SYNTHESIZE_FOREIGN
, &getpwent_data
.iterator
);
624 return r
< 0 ? NSS_STATUS_UNAVAIL
: NSS_STATUS_SUCCESS
;
627 enum nss_status
_nss_systemd_setgrent(int stayopen
) {
631 NSS_ENTRYPOINT_BEGIN
;
633 if (_nss_systemd_is_blocked())
634 return NSS_STATUS_NOTFOUND
;
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. */
639 getgrent_data
.iterator
= userdb_iterator_free(getgrent_data
.iterator
);
640 getgrent_data
.by_membership
= false;
642 /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE_INTRINSIC here */
643 r
= groupdb_all(/* match= */ NULL
, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC
| USERDB_DONT_SYNTHESIZE_FOREIGN
, &getgrent_data
.iterator
);
644 return r
< 0 ? NSS_STATUS_UNAVAIL
: NSS_STATUS_SUCCESS
;
647 enum nss_status
_nss_systemd_setspent(int stayopen
) {
651 NSS_ENTRYPOINT_BEGIN
;
653 if (_nss_systemd_is_blocked())
654 return NSS_STATUS_NOTFOUND
;
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. */
659 getspent_data
.iterator
= userdb_iterator_free(getspent_data
.iterator
);
660 getspent_data
.by_membership
= false;
662 /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE_INTRINSIC here */
663 r
= userdb_all(/* match= */ NULL
, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC
| USERDB_DONT_SYNTHESIZE_FOREIGN
, &getspent_data
.iterator
);
664 return r
< 0 ? NSS_STATUS_UNAVAIL
: NSS_STATUS_SUCCESS
;
667 enum nss_status
_nss_systemd_setsgent(int stayopen
) {
671 NSS_ENTRYPOINT_BEGIN
;
673 if (_nss_systemd_is_blocked())
674 return NSS_STATUS_NOTFOUND
;
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. */
679 getsgent_data
.iterator
= userdb_iterator_free(getsgent_data
.iterator
);
680 getsgent_data
.by_membership
= false;
682 /* See _nss_systemd_setpwent() for an explanation why we use USERDB_DONT_SYNTHESIZE here */
683 r
= groupdb_all(/* match= */ NULL
, nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE_INTRINSIC
| USERDB_DONT_SYNTHESIZE_FOREIGN
, &getsgent_data
.iterator
);
684 return r
< 0 ? NSS_STATUS_UNAVAIL
: NSS_STATUS_SUCCESS
;
687 enum nss_status
_nss_systemd_getpwent_r(
688 struct passwd
*result
,
689 char *buffer
, size_t buflen
,
692 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
696 NSS_ENTRYPOINT_BEGIN
;
701 if (_nss_systemd_is_blocked())
702 return NSS_STATUS_NOTFOUND
;
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. */
707 if (!getpwent_data
.iterator
) {
710 return NSS_STATUS_UNAVAIL
;
713 r
= userdb_iterator_get(getpwent_data
.iterator
, /* match= */ NULL
, &ur
);
715 return NSS_STATUS_NOTFOUND
;
719 return NSS_STATUS_UNAVAIL
;
722 r
= nss_pack_user_record(ur
, result
, buffer
, buflen
);
726 return NSS_STATUS_TRYAGAIN
;
729 return NSS_STATUS_SUCCESS
;
732 enum nss_status
_nss_systemd_getgrent_r(
733 struct group
*result
,
734 char *buffer
, size_t buflen
,
737 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
738 _cleanup_free_
char **members
= NULL
;
742 NSS_ENTRYPOINT_BEGIN
;
747 if (_nss_systemd_is_blocked())
748 return NSS_STATUS_NOTFOUND
;
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. */
753 if (!getgrent_data
.iterator
) {
756 return NSS_STATUS_UNAVAIL
;
759 if (!getgrent_data
.by_membership
) {
760 r
= groupdb_iterator_get(getgrent_data
.iterator
, /* match= */ NULL
, &gr
);
762 /* So we finished iterating native groups now. Let's now continue with iterating
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
);
770 r
= membershipdb_all(nss_glue_userdb_flags(), &getgrent_data
.iterator
);
771 if (r
< 0 && r
!= -ESRCH
) {
774 return NSS_STATUS_UNAVAIL
;
777 getgrent_data
.by_membership
= true;
781 return NSS_STATUS_UNAVAIL
;
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
);
784 if (r
< 0 && r
!= -ESRCH
) {
787 return NSS_STATUS_UNAVAIL
;
792 if (getgrent_data
.by_membership
) {
793 _cleanup_(_nss_systemd_unblockp
) bool blocked
= false;
795 if (!getgrent_data
.iterator
)
796 return NSS_STATUS_NOTFOUND
;
799 _cleanup_free_
char *user_name
= NULL
, *group_name
= NULL
;
801 r
= membershipdb_iterator_get(getgrent_data
.iterator
, &user_name
, &group_name
);
803 return NSS_STATUS_NOTFOUND
;
807 return NSS_STATUS_UNAVAIL
;
810 if (STR_IN_SET(user_name
, root_passwd
.pw_name
, nobody_passwd
.pw_name
))
812 if (STR_IN_SET(group_name
, root_group
.gr_name
, nobody_group
.gr_name
))
815 /* We are about to recursively call into NSS, let's make sure we disable recursion into our own code. */
817 r
= _nss_systemd_block(true);
821 return NSS_STATUS_UNAVAIL
;
827 r
= nss_group_record_by_name(group_name
, false, &gr
);
831 log_debug_errno(r
, "Failed to do NSS check for group '%s', ignoring: %m", group_name
);
835 members
= strv_new(user_name
);
839 return NSS_STATUS_TRYAGAIN
;
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. */
849 r
= nss_pack_group_record(gr
, members
, result
, buffer
, buflen
);
853 return NSS_STATUS_TRYAGAIN
;
856 return NSS_STATUS_SUCCESS
;
859 enum nss_status
_nss_systemd_getspent_r(
861 char *buffer
, size_t buflen
,
864 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
868 NSS_ENTRYPOINT_BEGIN
;
873 if (_nss_systemd_is_blocked())
874 return NSS_STATUS_NOTFOUND
;
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. */
879 if (!getspent_data
.iterator
) {
882 return NSS_STATUS_UNAVAIL
;
886 r
= userdb_iterator_get(getspent_data
.iterator
, /* match= */ NULL
, &ur
);
888 return NSS_STATUS_NOTFOUND
;
892 return NSS_STATUS_UNAVAIL
;
895 if (!ur
->incomplete
) /* don't synthesize shadow records for records where we couldn't read shadow data */
898 ur
= user_record_unref(ur
);
901 r
= nss_pack_user_record_shadow(ur
, result
, buffer
, buflen
);
905 return NSS_STATUS_TRYAGAIN
;
908 return NSS_STATUS_SUCCESS
;
911 enum nss_status
_nss_systemd_getsgent_r(
913 char *buffer
, size_t buflen
,
916 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
920 NSS_ENTRYPOINT_BEGIN
;
925 if (_nss_systemd_is_blocked())
926 return NSS_STATUS_NOTFOUND
;
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. */
931 if (!getsgent_data
.iterator
) {
934 return NSS_STATUS_UNAVAIL
;
938 r
= groupdb_iterator_get(getsgent_data
.iterator
, /* match= */ NULL
, &gr
);
940 return NSS_STATUS_NOTFOUND
;
944 return NSS_STATUS_UNAVAIL
;
947 if (!gr
->incomplete
) /* don't synthesize shadow records for records where we couldn't read shadow data */
950 gr
= group_record_unref(gr
);
953 r
= nss_pack_group_record_shadow(gr
, result
, buffer
, buflen
);
957 return NSS_STATUS_TRYAGAIN
;
960 return NSS_STATUS_SUCCESS
;
963 enum nss_status
_nss_systemd_initgroups_dyn(
964 const char *user_name
,
972 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
977 NSS_ENTRYPOINT_BEGIN
;
985 if (!valid_user_group_name(user_name
, VALID_USER_RELAX
))
986 return NSS_STATUS_NOTFOUND
;
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
;
992 if (_nss_systemd_is_blocked())
993 return NSS_STATUS_NOTFOUND
;
995 r
= membershipdb_by_user(user_name
, nss_glue_userdb_flags(), &iterator
);
999 return NSS_STATUS_UNAVAIL
;
1003 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
1004 _cleanup_free_
char *group_name
= NULL
;
1006 r
= membershipdb_iterator_get(iterator
, NULL
, &group_name
);
1012 return NSS_STATUS_UNAVAIL
;
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. */
1018 r
= groupdb_by_name(group_name
, /* match= */ NULL
, (nss_glue_userdb_flags() & ~USERDB_EXCLUDE_NSS
) | USERDB_SUPPRESS_SHADOW
, &g
);
1022 log_debug_errno(r
, "Failed to resolve group '%s', ignoring: %m", group_name
);
1029 if (*start
>= *size
) {
1033 if (limit
> 0 && *size
>= limit
) /* Reached the limit.? */
1036 if (*size
> LONG_MAX
/2) { /* Check for overflow */
1039 return NSS_STATUS_TRYAGAIN
;
1042 new_size
= *start
* 2;
1043 if (limit
> 0 && new_size
> limit
)
1046 /* Enlarge buffer */
1047 new_groups
= reallocarray(*groupsp
, new_size
, sizeof(**groupsp
));
1051 return NSS_STATUS_TRYAGAIN
;
1054 *groupsp
= new_groups
;
1058 (*groupsp
)[(*start
)++] = g
->gid
;
1062 return any
? NSS_STATUS_SUCCESS
: NSS_STATUS_NOTFOUND
;
1065 static thread_local
unsigned _blocked
= 0;
1067 _public_
int _nss_systemd_block(bool b
) {
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`. */
1073 if (_blocked
>= UINT_MAX
)
1084 return b
; /* Return what is passed in, i.e. the new state from the PoV of the caller */
1087 _public_
bool _nss_systemd_is_blocked(void) {
1088 return _blocked
> 0;