1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "errno-util.h"
11 #include "nss-systemd.h"
13 #include "pthread-util.h"
14 #include "signal-util.h"
16 #include "user-record-nss.h"
17 #include "user-util.h"
18 #include "userdb-glue.h"
21 static const struct passwd root_passwd
= {
22 .pw_name
= (char*) "root",
23 .pw_passwd
= (char*) "x", /* see shadow file */
26 .pw_gecos
= (char*) "Super User",
27 .pw_dir
= (char*) "/root",
28 .pw_shell
= (char*) "/bin/sh",
31 static const struct passwd nobody_passwd
= {
32 .pw_name
= (char*) NOBODY_USER_NAME
,
33 .pw_passwd
= (char*) "*", /* locked */
36 .pw_gecos
= (char*) "User Nobody",
37 .pw_dir
= (char*) "/",
38 .pw_shell
= (char*) NOLOGIN
,
41 static const struct group root_group
= {
42 .gr_name
= (char*) "root",
44 .gr_passwd
= (char*) "x", /* see shadow file */
45 .gr_mem
= (char*[]) { NULL
},
48 static const struct group nobody_group
= {
49 .gr_name
= (char*) NOBODY_GROUP_NAME
,
51 .gr_passwd
= (char*) "*", /* locked */
52 .gr_mem
= (char*[]) { NULL
},
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
;
63 /* Applies to group iterations only: true while we iterate over groups defined through NSS, false
68 static GetentData getpwent_data
= {
69 .mutex
= PTHREAD_MUTEX_INITIALIZER
72 static GetentData getgrent_data
= {
73 .mutex
= PTHREAD_MUTEX_INITIALIZER
76 static void setup_logging(void) {
77 /* We need a dummy function because log_parse_environment is a macro. */
78 log_parse_environment();
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);
86 #define NSS_ENTRYPOINT_BEGIN \
87 BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); \
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
);
96 enum nss_status
_nss_systemd_getpwnam_r(
99 char *buffer
, size_t buflen
,
102 enum nss_status status
;
106 NSS_ENTRYPOINT_BEGIN
;
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
;
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) {
121 if (streq(name
, root_passwd
.pw_name
)) {
123 return NSS_STATUS_SUCCESS
;
126 if (streq(name
, nobody_passwd
.pw_name
)) {
127 if (!synthesize_nobody())
128 return NSS_STATUS_NOTFOUND
;
130 *pwd
= nobody_passwd
;
131 return NSS_STATUS_SUCCESS
;
134 } else if (STR_IN_SET(name
, root_passwd
.pw_name
, nobody_passwd
.pw_name
))
135 return NSS_STATUS_NOTFOUND
;
137 status
= userdb_getpwnam(name
, pwd
, buffer
, buflen
, &e
);
138 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
147 enum nss_status
_nss_systemd_getpwuid_r(
150 char *buffer
, size_t buflen
,
153 enum nss_status status
;
157 NSS_ENTRYPOINT_BEGIN
;
162 if (!uid_is_valid(uid
))
163 return NSS_STATUS_NOTFOUND
;
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) {
168 if (uid
== root_passwd
.pw_uid
) {
170 return NSS_STATUS_SUCCESS
;
173 if (uid
== nobody_passwd
.pw_uid
) {
174 if (!synthesize_nobody())
175 return NSS_STATUS_NOTFOUND
;
177 *pwd
= nobody_passwd
;
178 return NSS_STATUS_SUCCESS
;
181 } else if (uid
== root_passwd
.pw_uid
|| uid
== nobody_passwd
.pw_uid
)
182 return NSS_STATUS_NOTFOUND
;
184 status
= userdb_getpwuid(uid
, pwd
, buffer
, buflen
, &e
);
185 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
194 #pragma GCC diagnostic ignored "-Wsizeof-pointer-memaccess"
196 enum nss_status
_nss_systemd_getgrnam_r(
199 char *buffer
, size_t buflen
,
202 enum nss_status status
;
206 NSS_ENTRYPOINT_BEGIN
;
212 if (!valid_user_group_name(name
, VALID_USER_RELAX
))
213 return NSS_STATUS_NOTFOUND
;
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) {
218 if (streq(name
, root_group
.gr_name
)) {
220 return NSS_STATUS_SUCCESS
;
223 if (streq(name
, nobody_group
.gr_name
)) {
224 if (!synthesize_nobody())
225 return NSS_STATUS_NOTFOUND
;
228 return NSS_STATUS_SUCCESS
;
231 } else if (STR_IN_SET(name
, root_group
.gr_name
, nobody_group
.gr_name
))
232 return NSS_STATUS_NOTFOUND
;
234 status
= userdb_getgrnam(name
, gr
, buffer
, buflen
, &e
);
235 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
244 enum nss_status
_nss_systemd_getgrgid_r(
247 char *buffer
, size_t buflen
,
250 enum nss_status status
;
254 NSS_ENTRYPOINT_BEGIN
;
259 if (!gid_is_valid(gid
))
260 return NSS_STATUS_NOTFOUND
;
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) {
265 if (gid
== root_group
.gr_gid
) {
267 return NSS_STATUS_SUCCESS
;
270 if (gid
== nobody_group
.gr_gid
) {
271 if (!synthesize_nobody())
272 return NSS_STATUS_NOTFOUND
;
275 return NSS_STATUS_SUCCESS
;
278 } else if (gid
== root_group
.gr_gid
|| gid
== nobody_group
.gr_gid
)
279 return NSS_STATUS_NOTFOUND
;
281 status
= userdb_getgrgid(gid
, gr
, buffer
, buflen
, &e
);
282 if (IN_SET(status
, NSS_STATUS_UNAVAIL
, NSS_STATUS_TRYAGAIN
)) {
291 static enum nss_status
nss_systemd_endent(GetentData
*p
) {
293 NSS_ENTRYPOINT_BEGIN
;
297 _cleanup_(pthread_mutex_unlock_assertp
) pthread_mutex_t
*_l
= NULL
;
298 _l
= pthread_mutex_lock_assert(&p
->mutex
);
300 p
->iterator
= userdb_iterator_free(p
->iterator
);
301 p
->by_membership
= false;
303 return NSS_STATUS_SUCCESS
;
306 enum nss_status
_nss_systemd_endpwent(void) {
307 return nss_systemd_endent(&getpwent_data
);
310 enum nss_status
_nss_systemd_endgrent(void) {
311 return nss_systemd_endent(&getgrent_data
);
314 enum nss_status
_nss_systemd_setpwent(int stayopen
) {
316 NSS_ENTRYPOINT_BEGIN
;
318 if (_nss_systemd_is_blocked())
319 return NSS_STATUS_NOTFOUND
;
321 _cleanup_(pthread_mutex_unlock_assertp
) pthread_mutex_t
*_l
= NULL
;
324 _l
= pthread_mutex_lock_assert(&getpwent_data
.mutex
);
326 getpwent_data
.iterator
= userdb_iterator_free(getpwent_data
.iterator
);
327 getpwent_data
.by_membership
= false;
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
334 r
= userdb_all(nss_glue_userdb_flags() | USERDB_DONT_SYNTHESIZE
, &getpwent_data
.iterator
);
335 return r
< 0 ? NSS_STATUS_UNAVAIL
: NSS_STATUS_SUCCESS
;
338 enum nss_status
_nss_systemd_setgrent(int stayopen
) {
340 NSS_ENTRYPOINT_BEGIN
;
342 if (_nss_systemd_is_blocked())
343 return NSS_STATUS_NOTFOUND
;
345 _cleanup_(pthread_mutex_unlock_assertp
) pthread_mutex_t
*_l
= NULL
;
348 _l
= pthread_mutex_lock_assert(&getgrent_data
.mutex
);
350 getgrent_data
.iterator
= userdb_iterator_free(getgrent_data
.iterator
);
351 getpwent_data
.by_membership
= false;
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
;
358 enum nss_status
_nss_systemd_getpwent_r(
359 struct passwd
*result
,
360 char *buffer
, size_t buflen
,
363 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
367 NSS_ENTRYPOINT_BEGIN
;
372 if (_nss_systemd_is_blocked())
373 return NSS_STATUS_NOTFOUND
;
375 _cleanup_(pthread_mutex_unlock_assertp
) pthread_mutex_t
*_l
= NULL
;
377 _l
= pthread_mutex_lock_assert(&getpwent_data
.mutex
);
379 if (!getpwent_data
.iterator
) {
382 return NSS_STATUS_UNAVAIL
;
385 r
= userdb_iterator_get(getpwent_data
.iterator
, &ur
);
387 return NSS_STATUS_NOTFOUND
;
391 return NSS_STATUS_UNAVAIL
;
394 r
= nss_pack_user_record(ur
, result
, buffer
, buflen
);
398 return NSS_STATUS_TRYAGAIN
;
401 return NSS_STATUS_SUCCESS
;
404 enum nss_status
_nss_systemd_getgrent_r(
405 struct group
*result
,
406 char *buffer
, size_t buflen
,
409 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
410 _cleanup_free_
char **members
= NULL
;
414 NSS_ENTRYPOINT_BEGIN
;
419 if (_nss_systemd_is_blocked())
420 return NSS_STATUS_NOTFOUND
;
422 _cleanup_(pthread_mutex_unlock_assertp
) pthread_mutex_t
*_l
= NULL
;
424 _l
= pthread_mutex_lock_assert(&getgrent_data
.mutex
);
426 if (!getgrent_data
.iterator
) {
429 return NSS_STATUS_UNAVAIL
;
432 if (!getgrent_data
.by_membership
) {
433 r
= groupdb_iterator_get(getgrent_data
.iterator
, &gr
);
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
);
443 r
= membershipdb_all(nss_glue_userdb_flags(), &getgrent_data
.iterator
);
447 return NSS_STATUS_UNAVAIL
;
450 getgrent_data
.by_membership
= true;
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
);
460 return NSS_STATUS_UNAVAIL
;
465 if (getgrent_data
.by_membership
) {
466 _cleanup_(_nss_systemd_unblockp
) bool blocked
= false;
469 _cleanup_free_
char *user_name
= NULL
, *group_name
= NULL
;
471 r
= membershipdb_iterator_get(getgrent_data
.iterator
, &user_name
, &group_name
);
473 return NSS_STATUS_NOTFOUND
;
477 return NSS_STATUS_UNAVAIL
;
480 if (STR_IN_SET(user_name
, root_passwd
.pw_name
, nobody_passwd
.pw_name
))
482 if (STR_IN_SET(group_name
, root_group
.gr_name
, nobody_group
.gr_name
))
485 /* We are about to recursively call into NSS, let's make sure we disable recursion into our own code. */
487 r
= _nss_systemd_block(true);
491 return NSS_STATUS_UNAVAIL
;
497 r
= nss_group_record_by_name(group_name
, false, &gr
);
501 log_debug_errno(r
, "Failed to do NSS check for group '%s', ignoring: %m", group_name
);
505 members
= strv_new(user_name
);
509 return NSS_STATUS_TRYAGAIN
;
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. */
519 r
= nss_pack_group_record(gr
, members
, result
, buffer
, buflen
);
523 return NSS_STATUS_TRYAGAIN
;
526 return NSS_STATUS_SUCCESS
;
529 enum nss_status
_nss_systemd_initgroups_dyn(
530 const char *user_name
,
538 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
543 NSS_ENTRYPOINT_BEGIN
;
551 if (!valid_user_group_name(user_name
, VALID_USER_RELAX
))
552 return NSS_STATUS_NOTFOUND
;
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
;
558 if (_nss_systemd_is_blocked())
559 return NSS_STATUS_NOTFOUND
;
561 r
= membershipdb_by_user(user_name
, nss_glue_userdb_flags(), &iterator
);
565 return NSS_STATUS_UNAVAIL
;
569 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
570 _cleanup_free_
char *group_name
= NULL
;
572 r
= membershipdb_iterator_get(iterator
, NULL
, &group_name
);
578 return NSS_STATUS_UNAVAIL
;
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. */
584 r
= groupdb_by_name(group_name
, (nss_glue_userdb_flags() & ~USERDB_AVOID_NSS
) | USERDB_AVOID_SHADOW
, &g
);
588 log_debug_errno(r
, "Failed to resolve group '%s', ignoring: %m", group_name
);
595 if (*start
>= *size
) {
599 if (limit
> 0 && *size
>= limit
) /* Reached the limit.? */
602 if (*size
> LONG_MAX
/2) { /* Check for overflow */
605 return NSS_STATUS_TRYAGAIN
;
608 new_size
= *start
* 2;
609 if (limit
> 0 && new_size
> limit
)
613 new_groups
= reallocarray(*groupsp
, new_size
, sizeof(**groupsp
));
617 return NSS_STATUS_TRYAGAIN
;
620 *groupsp
= new_groups
;
624 (*groupsp
)[(*start
)++] = g
->gid
;
628 return any
? NSS_STATUS_SUCCESS
: NSS_STATUS_NOTFOUND
;
631 static thread_local
unsigned _blocked
= 0;
633 _public_
int _nss_systemd_block(bool b
) {
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`. */
639 if (_blocked
>= UINT_MAX
)
650 return b
; /* Return what is passed in, i.e. the new state from the PoV of the caller */
653 _public_
bool _nss_systemd_is_blocked(void) {