]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/user-record-nss.c
user-record-nss: make return values optional
[thirdparty/systemd.git] / src / shared / user-record-nss.c
CommitLineData
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
5c0b7380
ZJS
14static inline const char* utf8_only(const char *s) {
15 return s && utf8_is_valid(s) ? s : NULL;
16}
17
18static inline int strv_extend_strv_utf8_only(char ***dst, char **src, bool filter_duplicates) {
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
38int 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
LP
138
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))));
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
168int 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
206int nss_user_record_by_name(
207 const char *name,
208 bool with_shadow,
209 UserRecord **ret) {
210
9b2d9078
LP
211 _cleanup_free_ char *buf = NULL, *sbuf = NULL;
212 struct passwd pwd, *result;
213 bool incomplete = false;
214 size_t buflen = 4096;
ed30170e 215 struct spwd spwd, *sresult = NULL;
9b2d9078
LP
216 int r;
217
218 assert(name);
9b2d9078
LP
219
220 for (;;) {
221 buf = malloc(buflen);
222 if (!buf)
223 return -ENOMEM;
224
225 r = getpwnam_r(name, &pwd, buf, buflen, &result);
226 if (r == 0) {
227 if (!result)
228 return -ESRCH;
229
230 break;
231 }
232
233 if (r < 0)
234 return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value");
235 if (r != ERANGE)
236 return -r;
237
238 if (buflen > SIZE_MAX / 2)
239 return -ERANGE;
240
241 buflen *= 2;
242 buf = mfree(buf);
243 }
244
ed30170e
LP
245 if (with_shadow) {
246 r = nss_spwd_for_passwd(result, &spwd, &sbuf);
247 if (r < 0) {
248 log_debug_errno(r, "Failed to do shadow lookup for user %s, ignoring: %m", name);
249 incomplete = ERRNO_IS_PRIVILEGE(r);
250 } else
251 sresult = &spwd;
252 } else
253 incomplete = true;
9b2d9078 254
ed30170e 255 r = nss_passwd_to_user_record(result, sresult, ret);
9b2d9078
LP
256 if (r < 0)
257 return r;
258
eb3641fc
LP
259 if (ret)
260 (*ret)->incomplete = incomplete;
9b2d9078
LP
261 return 0;
262}
263
ed30170e
LP
264int nss_user_record_by_uid(
265 uid_t uid,
266 bool with_shadow,
267 UserRecord **ret) {
268
9b2d9078
LP
269 _cleanup_free_ char *buf = NULL, *sbuf = NULL;
270 struct passwd pwd, *result;
271 bool incomplete = false;
272 size_t buflen = 4096;
ed30170e 273 struct spwd spwd, *sresult = NULL;
9b2d9078
LP
274 int r;
275
9b2d9078
LP
276 for (;;) {
277 buf = malloc(buflen);
278 if (!buf)
279 return -ENOMEM;
280
281 r = getpwuid_r(uid, &pwd, buf, buflen, &result);
282 if (r == 0) {
283 if (!result)
284 return -ESRCH;
285
286 break;
287 }
288 if (r < 0)
289 return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value");
290 if (r != ERANGE)
291 return -r;
292
293 if (buflen > SIZE_MAX / 2)
294 return -ERANGE;
295
296 buflen *= 2;
297 buf = mfree(buf);
298 }
299
ed30170e
LP
300 if (with_shadow) {
301 r = nss_spwd_for_passwd(result, &spwd, &sbuf);
302 if (r < 0) {
303 log_debug_errno(r, "Failed to do shadow lookup for UID " UID_FMT ", ignoring: %m", uid);
304 incomplete = ERRNO_IS_PRIVILEGE(r);
305 } else
306 sresult = &spwd;
307 } else
308 incomplete = true;
9b2d9078 309
ed30170e 310 r = nss_passwd_to_user_record(result, sresult, ret);
9b2d9078
LP
311 if (r < 0)
312 return r;
313
eb3641fc
LP
314 if (ret)
315 (*ret)->incomplete = incomplete;
9b2d9078
LP
316 return 0;
317}
e60775cb
ZJS
318
319int nss_group_to_group_record(
320 const struct group *grp,
321 const struct sgrp *sgrp,
322 GroupRecord **ret) {
323
324 _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
325 int r;
326
327 assert(grp);
e60775cb
ZJS
328
329 if (isempty(grp->gr_name))
330 return -EINVAL;
331
332 if (sgrp && !streq_ptr(sgrp->sg_namp, grp->gr_name))
333 return -EINVAL;
334
335 g = group_record_new();
336 if (!g)
337 return -ENOMEM;
338
339 g->group_name = strdup(grp->gr_name);
340 if (!g->group_name)
341 return -ENOMEM;
342
5c0b7380
ZJS
343 r = strv_extend_strv_utf8_only(&g->members, grp->gr_mem, false);
344 if (r < 0)
345 return r;
e60775cb
ZJS
346
347 g->gid = grp->gr_gid;
348
349 if (sgrp) {
5c0b7380 350 if (looks_like_hashed_password(utf8_only(sgrp->sg_passwd))) {
e60775cb
ZJS
351 g->hashed_password = strv_new(sgrp->sg_passwd);
352 if (!g->hashed_password)
353 return -ENOMEM;
354 }
355
5c0b7380 356 r = strv_extend_strv_utf8_only(&g->members, sgrp->sg_mem, true);
e60775cb
ZJS
357 if (r < 0)
358 return r;
359
5c0b7380
ZJS
360 r = strv_extend_strv_utf8_only(&g->administrators, sgrp->sg_adm, false);
361 if (r < 0)
362 return r;
e60775cb
ZJS
363 }
364
365 r = json_build(&g->json, JSON_BUILD_OBJECT(
366 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(g->group_name)),
367 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(g->gid)),
368 JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->members), "members", JSON_BUILD_STRV(g->members)),
369 JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->hashed_password), "privileged", JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRV(g->hashed_password)))),
370 JSON_BUILD_PAIR_CONDITION(!strv_isempty(g->administrators), "administrators", JSON_BUILD_STRV(g->administrators))));
371 if (r < 0)
372 return r;
373
374 g->mask = USER_RECORD_REGULAR |
375 (!strv_isempty(g->hashed_password) ? USER_RECORD_PRIVILEGED : 0);
376
eb3641fc
LP
377 if (ret)
378 *ret = TAKE_PTR(g);
e60775cb
ZJS
379 return 0;
380}
381
382int nss_sgrp_for_group(const struct group *grp, struct sgrp *ret_sgrp, char **ret_buffer) {
383 size_t buflen = 4096;
384 int r;
385
386 assert(grp);
387 assert(ret_sgrp);
388 assert(ret_buffer);
389
390 for (;;) {
391 _cleanup_free_ char *buf = NULL;
392 struct sgrp sgrp, *result;
393
394 buf = malloc(buflen);
395 if (!buf)
396 return -ENOMEM;
397
398 r = getsgnam_r(grp->gr_name, &sgrp, buf, buflen, &result);
399 if (r == 0) {
400 if (!result)
401 return -ESRCH;
402
403 *ret_sgrp = *result;
404 *ret_buffer = TAKE_PTR(buf);
405 return 0;
406 }
407 if (r < 0)
408 return -EIO; /* Weird, this should not return negative! */
409 if (r != ERANGE)
410 return -r;
411
412 if (buflen > SIZE_MAX / 2)
413 return -ERANGE;
414
415 buflen *= 2;
416 buf = mfree(buf);
417 }
418}
419
420int nss_group_record_by_name(
421 const char *name,
422 bool with_shadow,
423 GroupRecord **ret) {
424
425 _cleanup_free_ char *buf = NULL, *sbuf = NULL;
426 struct group grp, *result;
427 bool incomplete = false;
428 size_t buflen = 4096;
429 struct sgrp sgrp, *sresult = NULL;
430 int r;
431
432 assert(name);
e60775cb
ZJS
433
434 for (;;) {
435 buf = malloc(buflen);
436 if (!buf)
437 return -ENOMEM;
438
439 r = getgrnam_r(name, &grp, buf, buflen, &result);
440 if (r == 0) {
441 if (!result)
442 return -ESRCH;
443
444 break;
445 }
446
447 if (r < 0)
448 return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value");
449 if (r != ERANGE)
450 return -r;
451 if (buflen > SIZE_MAX / 2)
452 return -ERANGE;
453
454 buflen *= 2;
455 buf = mfree(buf);
456 }
457
458 if (with_shadow) {
459 r = nss_sgrp_for_group(result, &sgrp, &sbuf);
460 if (r < 0) {
461 log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name);
462 incomplete = ERRNO_IS_PRIVILEGE(r);
463 } else
464 sresult = &sgrp;
465 } else
466 incomplete = true;
467
468 r = nss_group_to_group_record(result, sresult, ret);
469 if (r < 0)
470 return r;
471
eb3641fc
LP
472 if (ret)
473 (*ret)->incomplete = incomplete;
e60775cb
ZJS
474 return 0;
475}
476
477int nss_group_record_by_gid(
478 gid_t gid,
479 bool with_shadow,
480 GroupRecord **ret) {
481
482 _cleanup_free_ char *buf = NULL, *sbuf = NULL;
483 struct group grp, *result;
484 bool incomplete = false;
485 size_t buflen = 4096;
486 struct sgrp sgrp, *sresult = NULL;
487 int r;
488
e60775cb
ZJS
489 for (;;) {
490 buf = malloc(buflen);
491 if (!buf)
492 return -ENOMEM;
493
494 r = getgrgid_r(gid, &grp, buf, buflen, &result);
495 if (r == 0) {
496 if (!result)
497 return -ESRCH;
498 break;
499 }
500
501 if (r < 0)
502 return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value");
503 if (r != ERANGE)
504 return -r;
505 if (buflen > SIZE_MAX / 2)
506 return -ERANGE;
507
508 buflen *= 2;
509 buf = mfree(buf);
510 }
511
512 if (with_shadow) {
513 r = nss_sgrp_for_group(result, &sgrp, &sbuf);
514 if (r < 0) {
515 log_debug_errno(r, "Failed to do shadow lookup for group %s, ignoring: %m", result->gr_name);
516 incomplete = ERRNO_IS_PRIVILEGE(r);
517 } else
518 sresult = &sgrp;
519 } else
520 incomplete = true;
521
522 r = nss_group_to_group_record(result, sresult, ret);
523 if (r < 0)
524 return r;
525
eb3641fc
LP
526 if (ret)
527 (*ret)->incomplete = incomplete;
e60775cb
ZJS
528 return 0;
529}