]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
9b2d9078 LP |
2 | |
3 | #include "errno-util.h" | |
4 | #include "format-util.h" | |
5 | #include "libcrypt-util.h" | |
6 | #include "strv.h" | |
7 | #include "user-record-nss.h" | |
5cd12aba | 8 | #include "user-util.h" |
5c0b7380 | 9 | #include "utf8.h" |
9b2d9078 | 10 | |
ddee3ada ZJS |
11 | #define SET_IF(field, condition, value, fallback) \ |
12 | field = (condition) ? (value) : (fallback) | |
13 | ||
cf1ab844 | 14 | static const char* utf8_only(const char *s) { |
5c0b7380 ZJS |
15 | return s && utf8_is_valid(s) ? s : NULL; |
16 | } | |
17 | ||
cf1ab844 | 18 | static int strv_extend_strv_utf8_only(char ***dst, char **src, bool filter_duplicates) { |
5c0b7380 ZJS |
19 | _cleanup_free_ char **t = NULL; |
20 | size_t l, j = 0; | |
21 | ||
22 | /* First, do a shallow copy of s, filtering for only valid utf-8 strings */ | |
23 | l = strv_length(src); | |
24 | t = new(char*, l + 1); | |
25 | if (!t) | |
26 | return -ENOMEM; | |
27 | ||
28 | for (size_t i = 0; i < l; i++) | |
29 | if (utf8_is_valid(src[i])) | |
30 | t[j++] = src[i]; | |
31 | if (j == 0) | |
32 | return 0; | |
33 | ||
34 | t[j] = NULL; | |
35 | return strv_extend_strv(dst, t, filter_duplicates); | |
36 | } | |
37 | ||
9b2d9078 LP |
38 | int nss_passwd_to_user_record( |
39 | const struct passwd *pwd, | |
40 | const struct spwd *spwd, | |
41 | UserRecord **ret) { | |
42 | ||
43 | _cleanup_(user_record_unrefp) UserRecord *hr = NULL; | |
44 | int r; | |
45 | ||
46 | assert(pwd); | |
9b2d9078 LP |
47 | |
48 | if (isempty(pwd->pw_name)) | |
49 | return -EINVAL; | |
50 | ||
51 | if (spwd && !streq_ptr(spwd->sp_namp, pwd->pw_name)) | |
52 | return -EINVAL; | |
53 | ||
54 | hr = user_record_new(); | |
55 | if (!hr) | |
56 | return -ENOMEM; | |
57 | ||
58 | r = free_and_strdup(&hr->user_name, pwd->pw_name); | |
59 | if (r < 0) | |
60 | return r; | |
61 | ||
5cd12aba LP |
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); | |
70 | if (r < 0) | |
71 | return r; | |
72 | } else { | |
73 | _cleanup_free_ char *mangled = NULL; | |
74 | ||
75 | mangled = mangle_gecos(pwd->pw_gecos); | |
76 | if (!mangled) | |
77 | return -ENOMEM; | |
78 | ||
79 | free_and_replace(hr->real_name, mangled); | |
80 | } | |
9b2d9078 | 81 | |
5c0b7380 | 82 | r = free_and_strdup(&hr->home_directory, utf8_only(empty_to_null(pwd->pw_dir))); |
192aee3c ZJS |
83 | if (r < 0) |
84 | return r; | |
9b2d9078 | 85 | |
5c0b7380 | 86 | r = free_and_strdup(&hr->shell, utf8_only(empty_to_null(pwd->pw_shell))); |
192aee3c ZJS |
87 | if (r < 0) |
88 | return r; | |
9b2d9078 LP |
89 | |
90 | hr->uid = pwd->pw_uid; | |
91 | hr->gid = pwd->pw_gid; | |
92 | ||
5c0b7380 ZJS |
93 | if (spwd && |
94 | looks_like_hashed_password(utf8_only(spwd->sp_pwdp))) { /* Ignore locked, disabled, and mojibake passwords */ | |
ddee3ada ZJS |
95 | strv_free_erase(hr->hashed_password); |
96 | hr->hashed_password = strv_new(spwd->sp_pwdp); | |
97 | if (!hr->hashed_password) | |
98 | return -ENOMEM; | |
99 | } else | |
9b2d9078 | 100 | hr->hashed_password = strv_free_erase(hr->hashed_password); |
ddee3ada ZJS |
101 | |
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. */ | |
106 | ||
107 | SET_IF(hr->locked, | |
108 | spwd && spwd->sp_expire >= 0, | |
109 | spwd->sp_expire <= 1, -1); | |
110 | ||
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); | |
114 | ||
115 | SET_IF(hr->password_change_now, | |
116 | spwd && spwd->sp_lstchg >= 0, | |
117 | spwd->sp_lstchg == 0, -1); | |
118 | ||
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); | |
122 | ||
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); | |
126 | ||
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); | |
130 | ||
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); | |
134 | ||
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); | |
9b2d9078 | 138 | |
309a747f LP |
139 | hr->json = sd_json_variant_unref(hr->json); |
140 | r = sd_json_build(&hr->json, SD_JSON_BUILD_OBJECT( | |
141 | SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(hr->user_name)), | |
142 | SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(hr->uid)), | |
143 | SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(hr->gid)), | |
144 | SD_JSON_BUILD_PAIR_CONDITION(!!hr->real_name, "realName", SD_JSON_BUILD_STRING(hr->real_name)), | |
145 | SD_JSON_BUILD_PAIR_CONDITION(!!hr->home_directory, "homeDirectory", SD_JSON_BUILD_STRING(hr->home_directory)), | |
146 | SD_JSON_BUILD_PAIR_CONDITION(!!hr->shell, "shell", SD_JSON_BUILD_STRING(hr->shell)), | |
147 | SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(hr->hashed_password), "privileged", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("hashedPassword", SD_JSON_BUILD_STRV(hr->hashed_password)))), | |
148 | SD_JSON_BUILD_PAIR_CONDITION(hr->locked >= 0, "locked", SD_JSON_BUILD_BOOLEAN(hr->locked)), | |
149 | SD_JSON_BUILD_PAIR_CONDITION(hr->not_after_usec != UINT64_MAX, "notAfterUSec", SD_JSON_BUILD_UNSIGNED(hr->not_after_usec)), | |
150 | SD_JSON_BUILD_PAIR_CONDITION(hr->password_change_now >= 0, "passwordChangeNow", SD_JSON_BUILD_BOOLEAN(hr->password_change_now)), | |
151 | SD_JSON_BUILD_PAIR_CONDITION(hr->last_password_change_usec != UINT64_MAX, "lastPasswordChangeUSec", SD_JSON_BUILD_UNSIGNED(hr->last_password_change_usec)), | |
152 | SD_JSON_BUILD_PAIR_CONDITION(hr->password_change_min_usec != UINT64_MAX, "passwordChangeMinUSec", SD_JSON_BUILD_UNSIGNED(hr->password_change_min_usec)), | |
153 | SD_JSON_BUILD_PAIR_CONDITION(hr->password_change_max_usec != UINT64_MAX, "passwordChangeMaxUSec", SD_JSON_BUILD_UNSIGNED(hr->password_change_max_usec)), | |
154 | SD_JSON_BUILD_PAIR_CONDITION(hr->password_change_warn_usec != UINT64_MAX, "passwordChangeWarnUSec", SD_JSON_BUILD_UNSIGNED(hr->password_change_warn_usec)), | |
155 | SD_JSON_BUILD_PAIR_CONDITION(hr->password_change_inactive_usec != UINT64_MAX, "passwordChangeInactiveUSec", SD_JSON_BUILD_UNSIGNED(hr->password_change_inactive_usec)))); | |
9b2d9078 LP |
156 | |
157 | if (r < 0) | |
158 | return r; | |
159 | ||
160 | hr->mask = USER_RECORD_REGULAR | | |
161 | (!strv_isempty(hr->hashed_password) ? USER_RECORD_PRIVILEGED : 0); | |
162 | ||
eb3641fc LP |
163 | if (ret) |
164 | *ret = TAKE_PTR(hr); | |
9b2d9078 LP |
165 | return 0; |
166 | } | |
167 | ||
168 | int nss_spwd_for_passwd(const struct passwd *pwd, struct spwd *ret_spwd, char **ret_buffer) { | |
169 | size_t buflen = 4096; | |
170 | int r; | |
171 | ||
172 | assert(pwd); | |
173 | assert(ret_spwd); | |
174 | assert(ret_buffer); | |
175 | ||
176 | for (;;) { | |
177 | _cleanup_free_ char *buf = NULL; | |
178 | struct spwd spwd, *result; | |
179 | ||
180 | buf = malloc(buflen); | |
181 | if (!buf) | |
182 | return -ENOMEM; | |
183 | ||
184 | r = getspnam_r(pwd->pw_name, &spwd, buf, buflen, &result); | |
185 | if (r == 0) { | |
186 | if (!result) | |
187 | return -ESRCH; | |
188 | ||
189 | *ret_spwd = *result; | |
190 | *ret_buffer = TAKE_PTR(buf); | |
191 | return 0; | |
192 | } | |
193 | if (r < 0) | |
194 | return -EIO; /* Weird, this should not return negative! */ | |
195 | if (r != ERANGE) | |
196 | return -r; | |
197 | ||
198 | if (buflen > SIZE_MAX / 2) | |
199 | return -ERANGE; | |
200 | ||
201 | buflen *= 2; | |
202 | buf = mfree(buf); | |
203 | } | |
204 | } | |
205 | ||
ed30170e LP |
206 | int nss_user_record_by_name( |
207 | const char *name, | |
208 | bool with_shadow, | |
209 | UserRecord **ret) { | |
210 | ||
75673cd8 LP |
211 | _cleanup_free_ char *sbuf = NULL; |
212 | _cleanup_free_ struct passwd *result = NULL; | |
9b2d9078 | 213 | bool incomplete = false; |
ed30170e | 214 | struct spwd spwd, *sresult = NULL; |
9b2d9078 LP |
215 | int r; |
216 | ||
217 | assert(name); | |
9b2d9078 | 218 | |
75673cd8 LP |
219 | r = getpwnam_malloc(name, &result); |
220 | if (r < 0) | |
221 | return r; | |
9b2d9078 | 222 | |
ed30170e LP |
223 | if (with_shadow) { |
224 | r = nss_spwd_for_passwd(result, &spwd, &sbuf); | |
225 | if (r < 0) { | |
226 | log_debug_errno(r, "Failed to do shadow lookup for user %s, ignoring: %m", name); | |
227 | incomplete = ERRNO_IS_PRIVILEGE(r); | |
228 | } else | |
229 | sresult = &spwd; | |
230 | } else | |
231 | incomplete = true; | |
9b2d9078 | 232 | |
ed30170e | 233 | r = nss_passwd_to_user_record(result, sresult, ret); |
9b2d9078 LP |
234 | if (r < 0) |
235 | return r; | |
236 | ||
eb3641fc LP |
237 | if (ret) |
238 | (*ret)->incomplete = incomplete; | |
9b2d9078 LP |
239 | return 0; |
240 | } | |
241 | ||
ed30170e LP |
242 | int nss_user_record_by_uid( |
243 | uid_t uid, | |
244 | bool with_shadow, | |
245 | UserRecord **ret) { | |
246 | ||
75673cd8 LP |
247 | _cleanup_free_ char *sbuf = NULL; |
248 | _cleanup_free_ struct passwd *result = NULL; | |
9b2d9078 | 249 | bool incomplete = false; |
ed30170e | 250 | struct spwd spwd, *sresult = NULL; |
9b2d9078 LP |
251 | int r; |
252 | ||
75673cd8 LP |
253 | r = getpwuid_malloc(uid, &result); |
254 | if (r < 0) | |
255 | return r; | |
9b2d9078 | 256 | |
ed30170e LP |
257 | if (with_shadow) { |
258 | r = nss_spwd_for_passwd(result, &spwd, &sbuf); | |
259 | if (r < 0) { | |
260 | log_debug_errno(r, "Failed to do shadow lookup for UID " UID_FMT ", ignoring: %m", uid); | |
261 | incomplete = ERRNO_IS_PRIVILEGE(r); | |
262 | } else | |
263 | sresult = &spwd; | |
264 | } else | |
265 | incomplete = true; | |
9b2d9078 | 266 | |
ed30170e | 267 | r = nss_passwd_to_user_record(result, sresult, ret); |
9b2d9078 LP |
268 | if (r < 0) |
269 | return r; | |
270 | ||
eb3641fc LP |
271 | if (ret) |
272 | (*ret)->incomplete = incomplete; | |
9b2d9078 LP |
273 | return 0; |
274 | } | |
e60775cb ZJS |
275 | |
276 | int nss_group_to_group_record( | |
277 | const struct group *grp, | |
278 | const struct sgrp *sgrp, | |
279 | GroupRecord **ret) { | |
280 | ||
281 | _cleanup_(group_record_unrefp) GroupRecord *g = NULL; | |
282 | int r; | |
283 | ||
284 | assert(grp); | |
e60775cb ZJS |
285 | |
286 | if (isempty(grp->gr_name)) | |
287 | return -EINVAL; | |
288 | ||
289 | if (sgrp && !streq_ptr(sgrp->sg_namp, grp->gr_name)) | |
290 | return -EINVAL; | |
291 | ||
292 | g = group_record_new(); | |
293 | if (!g) | |
294 | return -ENOMEM; | |
295 | ||
296 | g->group_name = strdup(grp->gr_name); | |
297 | if (!g->group_name) | |
298 | return -ENOMEM; | |
299 | ||
5c0b7380 ZJS |
300 | r = strv_extend_strv_utf8_only(&g->members, grp->gr_mem, false); |
301 | if (r < 0) | |
302 | return r; | |
e60775cb ZJS |
303 | |
304 | g->gid = grp->gr_gid; | |
305 | ||
306 | if (sgrp) { | |
5c0b7380 | 307 | if (looks_like_hashed_password(utf8_only(sgrp->sg_passwd))) { |
e60775cb ZJS |
308 | g->hashed_password = strv_new(sgrp->sg_passwd); |
309 | if (!g->hashed_password) | |
310 | return -ENOMEM; | |
311 | } | |
312 | ||
5c0b7380 | 313 | r = strv_extend_strv_utf8_only(&g->members, sgrp->sg_mem, true); |
e60775cb ZJS |
314 | if (r < 0) |
315 | return r; | |
316 | ||
5c0b7380 ZJS |
317 | r = strv_extend_strv_utf8_only(&g->administrators, sgrp->sg_adm, false); |
318 | if (r < 0) | |
319 | return r; | |
e60775cb ZJS |
320 | } |
321 | ||
309a747f LP |
322 | r = sd_json_build(&g->json, SD_JSON_BUILD_OBJECT( |
323 | SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(g->group_name)), | |
324 | SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(g->gid)), | |
325 | SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->members), "members", SD_JSON_BUILD_STRV(g->members)), | |
326 | SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->hashed_password), "privileged", SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("hashedPassword", SD_JSON_BUILD_STRV(g->hashed_password)))), | |
327 | SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->administrators), "administrators", SD_JSON_BUILD_STRV(g->administrators)))); | |
e60775cb ZJS |
328 | if (r < 0) |
329 | return r; | |
330 | ||
331 | g->mask = USER_RECORD_REGULAR | | |
332 | (!strv_isempty(g->hashed_password) ? USER_RECORD_PRIVILEGED : 0); | |
333 | ||
eb3641fc LP |
334 | if (ret) |
335 | *ret = TAKE_PTR(g); | |
e60775cb ZJS |
336 | return 0; |
337 | } | |
338 | ||
339 | int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer) { | |
340 | size_t buflen = 4096; | |
341 | int r; | |
342 | ||
343 | assert(grp); | |
344 | assert(ret_sgrp); | |
345 | assert(ret_buffer); | |
346 | ||
347 | for (;;) { | |
348 | _cleanup_free_ char *buf = NULL; | |
349 | struct sgrp sgrp, *result; | |
350 | ||
351 | buf = malloc(buflen); | |
352 | if (!buf) | |
353 | return -ENOMEM; | |
354 | ||
355 | r = getsgnam_r(grp->gr_name, &sgrp, buf, buflen, &result); | |
356 | if (r == 0) { | |
357 | if (!result) | |
358 | return -ESRCH; | |
359 | ||
360 | *ret_sgrp = *result; | |
361 | *ret_buffer = TAKE_PTR(buf); | |
362 | return 0; | |
363 | } | |
364 | if (r < 0) | |
365 | return -EIO; /* Weird, this should not return negative! */ | |
366 | if (r != ERANGE) | |
367 | return -r; | |
368 | ||
369 | if (buflen > SIZE_MAX / 2) | |
370 | return -ERANGE; | |
371 | ||
372 | buflen *= 2; | |
373 | buf = mfree(buf); | |
374 | } | |
375 | } | |
376 | ||
377 | int nss_group_record_by_name( | |
378 | const char *name, | |
379 | bool with_shadow, | |
380 | GroupRecord **ret) { | |
381 | ||
75673cd8 LP |
382 | _cleanup_free_ char *sbuf = NULL; |
383 | _cleanup_free_ struct group *result = NULL; | |
e60775cb | 384 | bool incomplete = false; |
e60775cb ZJS |
385 | struct sgrp sgrp, *sresult = NULL; |
386 | int r; | |
387 | ||
388 | assert(name); | |
e60775cb | 389 | |
75673cd8 LP |
390 | r = getgrnam_malloc(name, &result); |
391 | if (r < 0) | |
392 | return r; | |
e60775cb ZJS |
393 | |
394 | if (with_shadow) { | |
395 | r = nss_sgrp_for_group(result, &sgrp, &sbuf); | |
396 | if (r < 0) { | |
397 | log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name); | |
398 | incomplete = ERRNO_IS_PRIVILEGE(r); | |
399 | } else | |
400 | sresult = &sgrp; | |
401 | } else | |
402 | incomplete = true; | |
403 | ||
404 | r = nss_group_to_group_record(result, sresult, ret); | |
405 | if (r < 0) | |
406 | return r; | |
407 | ||
eb3641fc LP |
408 | if (ret) |
409 | (*ret)->incomplete = incomplete; | |
e60775cb ZJS |
410 | return 0; |
411 | } | |
412 | ||
413 | int nss_group_record_by_gid( | |
414 | gid_t gid, | |
415 | bool with_shadow, | |
416 | GroupRecord **ret) { | |
417 | ||
75673cd8 LP |
418 | _cleanup_free_ char *sbuf = NULL; |
419 | _cleanup_free_ struct group *result = NULL; | |
e60775cb | 420 | bool incomplete = false; |
e60775cb ZJS |
421 | struct sgrp sgrp, *sresult = NULL; |
422 | int r; | |
423 | ||
75673cd8 LP |
424 | r = getgrgid_malloc(gid, &result); |
425 | if (r < 0) | |
426 | return r; | |
e60775cb ZJS |
427 | |
428 | if (with_shadow) { | |
429 | r = nss_sgrp_for_group(result, &sgrp, &sbuf); | |
430 | if (r < 0) { | |
431 | log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name); | |
432 | incomplete = ERRNO_IS_PRIVILEGE(r); | |
433 | } else | |
434 | sresult = &sgrp; | |
435 | } else | |
436 | incomplete = true; | |
437 | ||
438 | r = nss_group_to_group_record(result, sresult, ret); | |
439 | if (r < 0) | |
440 | return r; | |
441 | ||
eb3641fc LP |
442 | if (ret) |
443 | (*ret)->incomplete = incomplete; | |
e60775cb ZJS |
444 | return 0; |
445 | } |