1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "errno-util.h"
4 #include "format-util.h"
5 #include "libcrypt-util.h"
7 #include "user-record-nss.h"
11 #define SET_IF(field, condition, value, fallback) \
12 field = (condition) ? (value) : (fallback)
14 static const char* utf8_only(const char *s
) {
15 return s
&& utf8_is_valid(s
) ? s
: NULL
;
18 static int strv_extend_strv_utf8_only(char ***dst
, char **src
, bool filter_duplicates
) {
19 _cleanup_free_
char **t
= NULL
;
22 /* First, do a shallow copy of s, filtering for only valid utf-8 strings */
24 t
= new(char*, l
+ 1);
28 for (size_t i
= 0; i
< l
; i
++)
29 if (utf8_is_valid(src
[i
]))
35 return strv_extend_strv(dst
, t
, filter_duplicates
);
38 int nss_passwd_to_user_record(
39 const struct passwd
*pwd
,
40 const struct spwd
*spwd
,
43 _cleanup_(user_record_unrefp
) UserRecord
*hr
= NULL
;
48 if (isempty(pwd
->pw_name
))
51 if (spwd
&& !streq_ptr(spwd
->sp_namp
, pwd
->pw_name
))
54 hr
= user_record_new();
58 r
= free_and_strdup(&hr
->user_name
, pwd
->pw_name
);
62 /* Some bad NSS modules synthesize GECOS fields with embedded ":" or "\n" characters, which are not
63 * something we can output in /etc/passwd compatible format, since these are record separators
64 * there. We normally refuse that, but we need to maintain compatibility with arbitrary NSS modules,
65 * hence let's do what glibc does: mangle the data to fit the format. */
66 if (isempty(pwd
->pw_gecos
) || streq_ptr(pwd
->pw_gecos
, hr
->user_name
))
67 hr
->real_name
= mfree(hr
->real_name
);
68 else if (valid_gecos(pwd
->pw_gecos
)) {
69 r
= free_and_strdup(&hr
->real_name
, pwd
->pw_gecos
);
73 _cleanup_free_
char *mangled
= NULL
;
75 mangled
= mangle_gecos(pwd
->pw_gecos
);
79 free_and_replace(hr
->real_name
, mangled
);
82 r
= free_and_strdup(&hr
->home_directory
, utf8_only(empty_to_null(pwd
->pw_dir
)));
86 r
= free_and_strdup(&hr
->shell
, utf8_only(empty_to_null(pwd
->pw_shell
)));
90 hr
->uid
= pwd
->pw_uid
;
91 hr
->gid
= pwd
->pw_gid
;
94 looks_like_hashed_password(utf8_only(spwd
->sp_pwdp
))) { /* Ignore locked, disabled, and mojibake passwords */
95 strv_free_erase(hr
->hashed_password
);
96 hr
->hashed_password
= strv_new(spwd
->sp_pwdp
);
97 if (!hr
->hashed_password
)
100 hr
->hashed_password
= strv_free_erase(hr
->hashed_password
);
102 /* shadow-utils suggests using "chage -E 0" (or -E 1, depending on which man page you check)
103 * for locking a whole account, hence check for that. Note that it also defines a way to lock
104 * just a password instead of the whole account, but that's mostly pointless in times of
105 * password-less authorization, hence let's not bother. */
108 spwd
&& spwd
->sp_expire
>= 0,
109 spwd
->sp_expire
<= 1, -1);
111 SET_IF(hr
->not_after_usec
,
112 spwd
&& spwd
->sp_expire
> 1 && (uint64_t) spwd
->sp_expire
< (UINT64_MAX
-1)/USEC_PER_DAY
,
113 spwd
->sp_expire
* USEC_PER_DAY
, UINT64_MAX
);
115 SET_IF(hr
->password_change_now
,
116 spwd
&& spwd
->sp_lstchg
>= 0,
117 spwd
->sp_lstchg
== 0, -1);
119 SET_IF(hr
->last_password_change_usec
,
120 spwd
&& spwd
->sp_lstchg
> 0 && (uint64_t) spwd
->sp_lstchg
<= (UINT64_MAX
-1)/USEC_PER_DAY
,
121 spwd
->sp_lstchg
* USEC_PER_DAY
, UINT64_MAX
);
123 SET_IF(hr
->password_change_min_usec
,
124 spwd
&& spwd
->sp_min
> 0 && (uint64_t) spwd
->sp_min
<= (UINT64_MAX
-1)/USEC_PER_DAY
,
125 spwd
->sp_min
* USEC_PER_DAY
, UINT64_MAX
);
127 SET_IF(hr
->password_change_max_usec
,
128 spwd
&& spwd
->sp_max
> 0 && (uint64_t) spwd
->sp_max
<= (UINT64_MAX
-1)/USEC_PER_DAY
,
129 spwd
->sp_max
* USEC_PER_DAY
, UINT64_MAX
);
131 SET_IF(hr
->password_change_warn_usec
,
132 spwd
&& spwd
->sp_warn
> 0 && (uint64_t) spwd
->sp_warn
<= (UINT64_MAX
-1)/USEC_PER_DAY
,
133 spwd
->sp_warn
* USEC_PER_DAY
, UINT64_MAX
);
135 SET_IF(hr
->password_change_inactive_usec
,
136 spwd
&& spwd
->sp_inact
> 0 && (uint64_t) spwd
->sp_inact
<= (UINT64_MAX
-1)/USEC_PER_DAY
,
137 spwd
->sp_inact
* USEC_PER_DAY
, UINT64_MAX
);
139 hr
->json
= json_variant_unref(hr
->json
);
140 r
= json_build(&hr
->json
, JSON_BUILD_OBJECT(
141 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(hr
->user_name
)),
142 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(hr
->uid
)),
143 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(hr
->gid
)),
144 JSON_BUILD_PAIR_CONDITION(hr
->real_name
, "realName", JSON_BUILD_STRING(hr
->real_name
)),
145 JSON_BUILD_PAIR_CONDITION(hr
->home_directory
, "homeDirectory", JSON_BUILD_STRING(hr
->home_directory
)),
146 JSON_BUILD_PAIR_CONDITION(hr
->shell
, "shell", JSON_BUILD_STRING(hr
->shell
)),
147 JSON_BUILD_PAIR_CONDITION(!strv_isempty(hr
->hashed_password
), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(hr
->hashed_password
)))),
148 JSON_BUILD_PAIR_CONDITION(hr
->locked
>= 0, "locked", JSON_BUILD_BOOLEAN(hr
->locked
)),
149 JSON_BUILD_PAIR_CONDITION(hr
->not_after_usec
!= UINT64_MAX
, "notAfterUSec", JSON_BUILD_UNSIGNED(hr
->not_after_usec
)),
150 JSON_BUILD_PAIR_CONDITION(hr
->password_change_now
>= 0, "passwordChangeNow", JSON_BUILD_BOOLEAN(hr
->password_change_now
)),
151 JSON_BUILD_PAIR_CONDITION(hr
->last_password_change_usec
!= UINT64_MAX
, "lastPasswordChangeUSec", JSON_BUILD_UNSIGNED(hr
->last_password_change_usec
)),
152 JSON_BUILD_PAIR_CONDITION(hr
->password_change_min_usec
!= UINT64_MAX
, "passwordChangeMinUSec", JSON_BUILD_UNSIGNED(hr
->password_change_min_usec
)),
153 JSON_BUILD_PAIR_CONDITION(hr
->password_change_max_usec
!= UINT64_MAX
, "passwordChangeMaxUSec", JSON_BUILD_UNSIGNED(hr
->password_change_max_usec
)),
154 JSON_BUILD_PAIR_CONDITION(hr
->password_change_warn_usec
!= UINT64_MAX
, "passwordChangeWarnUSec", JSON_BUILD_UNSIGNED(hr
->password_change_warn_usec
)),
155 JSON_BUILD_PAIR_CONDITION(hr
->password_change_inactive_usec
!= UINT64_MAX
, "passwordChangeInactiveUSec", JSON_BUILD_UNSIGNED(hr
->password_change_inactive_usec
))));
160 hr
->mask
= USER_RECORD_REGULAR
|
161 (!strv_isempty(hr
->hashed_password
) ? USER_RECORD_PRIVILEGED
: 0);
168 int nss_spwd_for_passwd(const struct passwd
*pwd
, struct spwd
*ret_spwd
, char **ret_buffer
) {
169 size_t buflen
= 4096;
177 _cleanup_free_
char *buf
= NULL
;
178 struct spwd spwd
, *result
;
180 buf
= malloc(buflen
);
184 r
= getspnam_r(pwd
->pw_name
, &spwd
, buf
, buflen
, &result
);
190 *ret_buffer
= TAKE_PTR(buf
);
194 return -EIO
; /* Weird, this should not return negative! */
198 if (buflen
> SIZE_MAX
/ 2)
206 int nss_user_record_by_name(
211 _cleanup_free_
char *sbuf
= NULL
;
212 _cleanup_free_
struct passwd
*result
= NULL
;
213 bool incomplete
= false;
214 struct spwd spwd
, *sresult
= NULL
;
219 r
= getpwnam_malloc(name
, &result
);
224 r
= nss_spwd_for_passwd(result
, &spwd
, &sbuf
);
226 log_debug_errno(r
, "Failed to do shadow lookup for user %s, ignoring: %m", name
);
227 incomplete
= ERRNO_IS_PRIVILEGE(r
);
233 r
= nss_passwd_to_user_record(result
, sresult
, ret
);
238 (*ret
)->incomplete
= incomplete
;
242 int nss_user_record_by_uid(
247 _cleanup_free_
char *sbuf
= NULL
;
248 _cleanup_free_
struct passwd
*result
= NULL
;
249 bool incomplete
= false;
250 struct spwd spwd
, *sresult
= NULL
;
253 r
= getpwuid_malloc(uid
, &result
);
258 r
= nss_spwd_for_passwd(result
, &spwd
, &sbuf
);
260 log_debug_errno(r
, "Failed to do shadow lookup for UID " UID_FMT
", ignoring: %m", uid
);
261 incomplete
= ERRNO_IS_PRIVILEGE(r
);
267 r
= nss_passwd_to_user_record(result
, sresult
, ret
);
272 (*ret
)->incomplete
= incomplete
;
276 int nss_group_to_group_record(
277 const struct group
*grp
,
278 const struct sgrp
*sgrp
,
281 _cleanup_(group_record_unrefp
) GroupRecord
*g
= NULL
;
286 if (isempty(grp
->gr_name
))
289 if (sgrp
&& !streq_ptr(sgrp
->sg_namp
, grp
->gr_name
))
292 g
= group_record_new();
296 g
->group_name
= strdup(grp
->gr_name
);
300 r
= strv_extend_strv_utf8_only(&g
->members
, grp
->gr_mem
, false);
304 g
->gid
= grp
->gr_gid
;
307 if (looks_like_hashed_password(utf8_only(sgrp
->sg_passwd
))) {
308 g
->hashed_password
= strv_new(sgrp
->sg_passwd
);
309 if (!g
->hashed_password
)
313 r
= strv_extend_strv_utf8_only(&g
->members
, sgrp
->sg_mem
, true);
317 r
= strv_extend_strv_utf8_only(&g
->administrators
, sgrp
->sg_adm
, false);
322 r
= json_build(&g
->json
, JSON_BUILD_OBJECT(
323 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g
->group_name
)),
324 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(g
->gid
)),
325 JSON_BUILD_PAIR_CONDITION(!strv_isempty(g
->members
), "members", JSON_BUILD_STRV(g
->members
)),
326 JSON_BUILD_PAIR_CONDITION(!strv_isempty(g
->hashed_password
), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(g
->hashed_password
)))),
327 JSON_BUILD_PAIR_CONDITION(!strv_isempty(g
->administrators
), "administrators", JSON_BUILD_STRV(g
->administrators
))));
331 g
->mask
= USER_RECORD_REGULAR
|
332 (!strv_isempty(g
->hashed_password
) ? USER_RECORD_PRIVILEGED
: 0);
339 int nss_sgrp_for_group(const struct group
*grp
, struct sgrp
*ret_sgrp
, char **ret_buffer
) {
340 size_t buflen
= 4096;
348 _cleanup_free_
char *buf
= NULL
;
349 struct sgrp sgrp
, *result
;
351 buf
= malloc(buflen
);
355 r
= getsgnam_r(grp
->gr_name
, &sgrp
, buf
, buflen
, &result
);
361 *ret_buffer
= TAKE_PTR(buf
);
365 return -EIO
; /* Weird, this should not return negative! */
369 if (buflen
> SIZE_MAX
/ 2)
377 int nss_group_record_by_name(
382 _cleanup_free_
char *sbuf
= NULL
;
383 _cleanup_free_
struct group
*result
= NULL
;
384 bool incomplete
= false;
385 struct sgrp sgrp
, *sresult
= NULL
;
390 r
= getgrnam_malloc(name
, &result
);
395 r
= nss_sgrp_for_group(result
, &sgrp
, &sbuf
);
397 log_debug_errno(r
, "Failed to do shadow lookup for group %s, ignoring: %m", result
->gr_name
);
398 incomplete
= ERRNO_IS_PRIVILEGE(r
);
404 r
= nss_group_to_group_record(result
, sresult
, ret
);
409 (*ret
)->incomplete
= incomplete
;
413 int nss_group_record_by_gid(
418 _cleanup_free_
char *sbuf
= NULL
;
419 _cleanup_free_
struct group
*result
= NULL
;
420 bool incomplete
= false;
421 struct sgrp sgrp
, *sresult
= NULL
;
424 r
= getgrgid_malloc(gid
, &result
);
429 r
= nss_sgrp_for_group(result
, &sgrp
, &sbuf
);
431 log_debug_errno(r
, "Failed to do shadow lookup for group %s, ignoring: %m", result
->gr_name
);
432 incomplete
= ERRNO_IS_PRIVILEGE(r
);
438 r
= nss_group_to_group_record(result
, sresult
, ret
);
443 (*ret
)->incomplete
= incomplete
;