]>
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 | ||
5c0b7380 ZJS |
14 | static inline const char* utf8_only(const char *s) { |
15 | return s && utf8_is_valid(s) ? s : NULL; | |
16 | } | |
17 | ||
18 | static 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 |
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 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 | ||
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 | ||
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 |
264 | int 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 | |
319 | int 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 | ||
382 | int 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 | ||
420 | int 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 | ||
477 | int 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 | } |