]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/userdb.c
Merge pull request #30661 from rpigott/resolved-https-record
[thirdparty/systemd.git] / src / shared / userdb.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/auxv.h>
4
5 #include "conf-files.h"
6 #include "dirent-util.h"
7 #include "dlfcn-util.h"
8 #include "errno-util.h"
9 #include "fd-util.h"
10 #include "format-util.h"
11 #include "missing_syscall.h"
12 #include "parse-util.h"
13 #include "set.h"
14 #include "socket-util.h"
15 #include "strv.h"
16 #include "user-record-nss.h"
17 #include "user-util.h"
18 #include "userdb-dropin.h"
19 #include "userdb.h"
20 #include "varlink.h"
21
22 DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, Varlink, varlink_unref);
23
24 typedef enum LookupWhat {
25 LOOKUP_USER,
26 LOOKUP_GROUP,
27 LOOKUP_MEMBERSHIP,
28 _LOOKUP_WHAT_MAX,
29 } LookupWhat;
30
31 struct UserDBIterator {
32 LookupWhat what;
33 UserDBFlags flags;
34 Set *links;
35 bool nss_covered:1;
36 bool nss_iterating:1;
37 bool dropin_covered:1;
38 bool synthesize_root:1;
39 bool synthesize_nobody:1;
40 bool nss_systemd_blocked:1;
41 char **dropins;
42 size_t current_dropin;
43 int error;
44 unsigned n_found;
45 sd_event *event;
46 UserRecord *found_user; /* when .what == LOOKUP_USER */
47 GroupRecord *found_group; /* when .what == LOOKUP_GROUP */
48
49 char *found_user_name, *found_group_name; /* when .what == LOOKUP_MEMBERSHIP */
50 char **members_of_group;
51 size_t index_members_of_group;
52 char *filter_user_name, *filter_group_name;
53 };
54
55 UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
56 if (!iterator)
57 return NULL;
58
59 set_free(iterator->links);
60 strv_free(iterator->dropins);
61
62 switch (iterator->what) {
63
64 case LOOKUP_USER:
65 user_record_unref(iterator->found_user);
66
67 if (iterator->nss_iterating)
68 endpwent();
69
70 break;
71
72 case LOOKUP_GROUP:
73 group_record_unref(iterator->found_group);
74
75 if (iterator->nss_iterating)
76 endgrent();
77
78 break;
79
80 case LOOKUP_MEMBERSHIP:
81 free(iterator->found_user_name);
82 free(iterator->found_group_name);
83 strv_free(iterator->members_of_group);
84 free(iterator->filter_user_name);
85 free(iterator->filter_group_name);
86
87 if (iterator->nss_iterating)
88 endgrent();
89
90 break;
91
92 default:
93 assert_not_reached();
94 }
95
96 sd_event_unref(iterator->event);
97
98 if (iterator->nss_systemd_blocked)
99 assert_se(userdb_block_nss_systemd(false) >= 0);
100
101 return mfree(iterator);
102 }
103
104 static UserDBIterator* userdb_iterator_new(LookupWhat what, UserDBFlags flags) {
105 UserDBIterator *i;
106
107 assert(what >= 0);
108 assert(what < _LOOKUP_WHAT_MAX);
109
110 i = new(UserDBIterator, 1);
111 if (!i)
112 return NULL;
113
114 *i = (UserDBIterator) {
115 .what = what,
116 .flags = flags,
117 .synthesize_root = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE),
118 .synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE),
119 };
120
121 return i;
122 }
123
124 static int userdb_iterator_block_nss_systemd(UserDBIterator *iterator) {
125 int r;
126
127 assert(iterator);
128
129 if (iterator->nss_systemd_blocked)
130 return 0;
131
132 r = userdb_block_nss_systemd(true);
133 if (r < 0)
134 return r;
135
136 iterator->nss_systemd_blocked = true;
137 return 1;
138 }
139
140 struct user_group_data {
141 JsonVariant *record;
142 bool incomplete;
143 };
144
145 static void user_group_data_done(struct user_group_data *d) {
146 json_variant_unref(d->record);
147 }
148
149 struct membership_data {
150 char *user_name;
151 char *group_name;
152 };
153
154 static void membership_data_done(struct membership_data *d) {
155 free(d->user_name);
156 free(d->group_name);
157 }
158
159 static int userdb_on_query_reply(
160 Varlink *link,
161 JsonVariant *parameters,
162 const char *error_id,
163 VarlinkReplyFlags flags,
164 void *userdata) {
165
166 UserDBIterator *iterator = ASSERT_PTR(userdata);
167 int r;
168
169 if (error_id) {
170 log_debug("Got lookup error: %s", error_id);
171
172 if (STR_IN_SET(error_id,
173 "io.systemd.UserDatabase.NoRecordFound",
174 "io.systemd.UserDatabase.ConflictingRecordFound"))
175 r = -ESRCH;
176 else if (streq(error_id, "io.systemd.UserDatabase.ServiceNotAvailable"))
177 r = -EHOSTDOWN;
178 else if (streq(error_id, "io.systemd.UserDatabase.EnumerationNotSupported"))
179 r = -EOPNOTSUPP;
180 else if (streq(error_id, VARLINK_ERROR_TIMEOUT))
181 r = -ETIMEDOUT;
182 else
183 r = -EIO;
184
185 goto finish;
186 }
187
188 switch (iterator->what) {
189
190 case LOOKUP_USER: {
191 _cleanup_(user_group_data_done) struct user_group_data user_data = {};
192
193 static const JsonDispatch dispatch_table[] = {
194 { "record", _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record), 0 },
195 { "incomplete", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct user_group_data, incomplete), 0 },
196 {}
197 };
198 _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
199
200 assert_se(!iterator->found_user);
201
202 r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &user_data);
203 if (r < 0)
204 goto finish;
205
206 if (!user_data.record) {
207 r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
208 goto finish;
209 }
210
211 hr = user_record_new();
212 if (!hr) {
213 r = -ENOMEM;
214 goto finish;
215 }
216
217 r = user_record_load(hr, user_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
218 if (r < 0)
219 goto finish;
220
221 if (!hr->service) {
222 r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "User record does not carry service information, refusing.");
223 goto finish;
224 }
225
226 hr->incomplete = user_data.incomplete;
227
228 /* We match the root user by the name since the name is our primary key. We match the nobody
229 * use by UID though, since the name might differ on OSes */
230 if (streq_ptr(hr->user_name, "root"))
231 iterator->synthesize_root = false;
232 if (hr->uid == UID_NOBODY)
233 iterator->synthesize_nobody = false;
234
235 iterator->found_user = TAKE_PTR(hr);
236 iterator->n_found++;
237
238 /* More stuff coming? then let's just exit cleanly here */
239 if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
240 return 0;
241
242 /* Otherwise, let's remove this link and exit cleanly then */
243 r = 0;
244 goto finish;
245 }
246
247 case LOOKUP_GROUP: {
248 _cleanup_(user_group_data_done) struct user_group_data group_data = {};
249
250 static const JsonDispatch dispatch_table[] = {
251 { "record", _JSON_VARIANT_TYPE_INVALID, json_dispatch_variant, offsetof(struct user_group_data, record), 0 },
252 { "incomplete", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct user_group_data, incomplete), 0 },
253 {}
254 };
255 _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
256
257 assert_se(!iterator->found_group);
258
259 r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &group_data);
260 if (r < 0)
261 goto finish;
262
263 if (!group_data.record) {
264 r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
265 goto finish;
266 }
267
268 g = group_record_new();
269 if (!g) {
270 r = -ENOMEM;
271 goto finish;
272 }
273
274 r = group_record_load(g, group_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
275 if (r < 0)
276 goto finish;
277
278 if (!g->service) {
279 r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Group record does not carry service information, refusing.");
280 goto finish;
281 }
282
283 g->incomplete = group_data.incomplete;
284
285 if (streq_ptr(g->group_name, "root"))
286 iterator->synthesize_root = false;
287 if (g->gid == GID_NOBODY)
288 iterator->synthesize_nobody = false;
289
290 iterator->found_group = TAKE_PTR(g);
291 iterator->n_found++;
292
293 if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
294 return 0;
295
296 r = 0;
297 goto finish;
298 }
299
300 case LOOKUP_MEMBERSHIP: {
301 _cleanup_(membership_data_done) struct membership_data membership_data = {};
302
303 static const JsonDispatch dispatch_table[] = {
304 { "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, user_name), JSON_RELAX },
305 { "groupName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, group_name), JSON_RELAX },
306 {}
307 };
308
309 assert(!iterator->found_user_name);
310 assert(!iterator->found_group_name);
311
312 r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &membership_data);
313 if (r < 0)
314 goto finish;
315
316 iterator->found_user_name = TAKE_PTR(membership_data.user_name);
317 iterator->found_group_name = TAKE_PTR(membership_data.group_name);
318 iterator->n_found++;
319
320 if (FLAGS_SET(flags, VARLINK_REPLY_CONTINUES))
321 return 0;
322
323 r = 0;
324 goto finish;
325 }
326
327 default:
328 assert_not_reached();
329 }
330
331 finish:
332 /* If we got one ESRCH, let that win. This way when we do a wild dump we won't be tripped up by bad
333 * errors if at least one connection ended cleanly */
334 if (r == -ESRCH || iterator->error == 0)
335 iterator->error = -r;
336
337 assert_se(set_remove(iterator->links, link) == link);
338 link = varlink_unref(link);
339 return 0;
340 }
341
342 static int userdb_connect(
343 UserDBIterator *iterator,
344 const char *path,
345 const char *method,
346 bool more,
347 JsonVariant *query) {
348
349 _cleanup_(varlink_unrefp) Varlink *vl = NULL;
350 int r;
351
352 assert(iterator);
353 assert(path);
354 assert(method);
355
356 r = varlink_connect_address(&vl, path);
357 if (r < 0)
358 return log_debug_errno(r, "Unable to connect to %s: %m", path);
359
360 varlink_set_userdata(vl, iterator);
361
362 if (!iterator->event) {
363 r = sd_event_new(&iterator->event);
364 if (r < 0)
365 return log_debug_errno(r, "Unable to allocate event loop: %m");
366 }
367
368 r = varlink_attach_event(vl, iterator->event, SD_EVENT_PRIORITY_NORMAL);
369 if (r < 0)
370 return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m");
371
372 (void) varlink_set_description(vl, path);
373
374 r = varlink_bind_reply(vl, userdb_on_query_reply);
375 if (r < 0)
376 return log_debug_errno(r, "Failed to bind reply callback: %m");
377
378 if (more)
379 r = varlink_observe(vl, method, query);
380 else
381 r = varlink_invoke(vl, method, query);
382 if (r < 0)
383 return log_debug_errno(r, "Failed to invoke varlink method: %m");
384
385 r = set_ensure_consume(&iterator->links, &link_hash_ops, TAKE_PTR(vl));
386 if (r < 0)
387 return log_debug_errno(r, "Failed to add varlink connection to set: %m");
388 return r;
389 }
390
391 static int userdb_start_query(
392 UserDBIterator *iterator,
393 const char *method,
394 bool more,
395 JsonVariant *query,
396 UserDBFlags flags) {
397
398 _cleanup_strv_free_ char **except = NULL, **only = NULL;
399 _cleanup_closedir_ DIR *d = NULL;
400 const char *e;
401 int r, ret = 0;
402
403 assert(iterator);
404 assert(method);
405
406 if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
407 return -ENOLINK;
408
409 e = getenv("SYSTEMD_BYPASS_USERDB");
410 if (e) {
411 r = parse_boolean(e);
412 if (r > 0)
413 return -ENOLINK;
414 if (r < 0) {
415 except = strv_split(e, ":");
416 if (!except)
417 return -ENOMEM;
418 }
419 }
420
421 e = getenv("SYSTEMD_ONLY_USERDB");
422 if (e) {
423 only = strv_split(e, ":");
424 if (!only)
425 return -ENOMEM;
426 }
427
428 /* First, let's talk to the multiplexer, if we can */
429 if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE)) == 0 &&
430 !strv_contains(except, "io.systemd.Multiplexer") &&
431 (!only || strv_contains(only, "io.systemd.Multiplexer"))) {
432 _cleanup_(json_variant_unrefp) JsonVariant *patched_query = json_variant_ref(query);
433
434 r = json_variant_set_field_string(&patched_query, "service", "io.systemd.Multiplexer");
435 if (r < 0)
436 return log_debug_errno(r, "Unable to set service JSON field: %m");
437
438 r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, patched_query);
439 if (r >= 0) {
440 iterator->nss_covered = true; /* The multiplexer does NSS */
441 iterator->dropin_covered = true; /* It also handles drop-in stuff */
442 return 0;
443 }
444 }
445
446 d = opendir("/run/systemd/userdb/");
447 if (!d) {
448 if (errno == ENOENT)
449 return -ESRCH;
450
451 return -errno;
452 }
453
454 FOREACH_DIRENT(de, d, return -errno) {
455 _cleanup_(json_variant_unrefp) JsonVariant *patched_query = NULL;
456 _cleanup_free_ char *p = NULL;
457 bool is_nss, is_dropin;
458
459 if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
460 continue;
461
462 if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
463 streq(de->d_name, "io.systemd.DynamicUser"))
464 continue;
465
466 /* Avoid NSS if this is requested. Note that we also skip NSS when we were asked to skip the
467 * multiplexer, since in that case it's safer to do NSS in the client side emulation below
468 * (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
469 * anyway). */
470 is_nss = streq(de->d_name, "io.systemd.NameServiceSwitch");
471 if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
472 continue;
473
474 /* Similar for the drop-in service */
475 is_dropin = streq(de->d_name, "io.systemd.DropIn");
476 if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
477 continue;
478
479 if (strv_contains(except, de->d_name))
480 continue;
481
482 if (only && !strv_contains(only, de->d_name))
483 continue;
484
485 p = path_join("/run/systemd/userdb/", de->d_name);
486 if (!p)
487 return -ENOMEM;
488
489 patched_query = json_variant_ref(query);
490 r = json_variant_set_field_string(&patched_query, "service", de->d_name);
491 if (r < 0)
492 return log_debug_errno(r, "Unable to set service JSON field: %m");
493
494 r = userdb_connect(iterator, p, method, more, patched_query);
495 if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
496 * and could connect to it */
497 iterator->nss_covered = true;
498 if (is_dropin && r >= 0)
499 iterator->dropin_covered = true;
500
501 if (ret == 0 && r < 0)
502 ret = r;
503 }
504
505 if (set_isempty(iterator->links))
506 return ret < 0 ? ret : -ESRCH; /* propagate last error we saw if we couldn't connect to anything. */
507
508 /* We connected to some services, in this case, ignore the ones we failed on */
509 return 0;
510 }
511
512 static int userdb_process(
513 UserDBIterator *iterator,
514 UserRecord **ret_user_record,
515 GroupRecord **ret_group_record,
516 char **ret_user_name,
517 char **ret_group_name) {
518
519 int r;
520
521 assert(iterator);
522
523 for (;;) {
524 if (iterator->what == LOOKUP_USER && iterator->found_user) {
525 if (ret_user_record)
526 *ret_user_record = TAKE_PTR(iterator->found_user);
527 else
528 iterator->found_user = user_record_unref(iterator->found_user);
529
530 if (ret_group_record)
531 *ret_group_record = NULL;
532 if (ret_user_name)
533 *ret_user_name = NULL;
534 if (ret_group_name)
535 *ret_group_name = NULL;
536
537 return 0;
538 }
539
540 if (iterator->what == LOOKUP_GROUP && iterator->found_group) {
541 if (ret_group_record)
542 *ret_group_record = TAKE_PTR(iterator->found_group);
543 else
544 iterator->found_group = group_record_unref(iterator->found_group);
545
546 if (ret_user_record)
547 *ret_user_record = NULL;
548 if (ret_user_name)
549 *ret_user_name = NULL;
550 if (ret_group_name)
551 *ret_group_name = NULL;
552
553 return 0;
554 }
555
556 if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
557 if (ret_user_name)
558 *ret_user_name = TAKE_PTR(iterator->found_user_name);
559 else
560 iterator->found_user_name = mfree(iterator->found_user_name);
561
562 if (ret_group_name)
563 *ret_group_name = TAKE_PTR(iterator->found_group_name);
564 else
565 iterator->found_group_name = mfree(iterator->found_group_name);
566
567 if (ret_user_record)
568 *ret_user_record = NULL;
569 if (ret_group_record)
570 *ret_group_record = NULL;
571
572 return 0;
573 }
574
575 if (set_isempty(iterator->links)) {
576 if (iterator->error == 0)
577 return -ESRCH;
578
579 return -abs(iterator->error);
580 }
581
582 if (!iterator->event)
583 return -ESRCH;
584
585 r = sd_event_run(iterator->event, UINT64_MAX);
586 if (r < 0)
587 return r;
588 }
589 }
590
591 static int synthetic_root_user_build(UserRecord **ret) {
592 return user_record_build(
593 ret,
594 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING("root")),
595 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(0)),
596 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
597 JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/root")),
598 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
599 }
600
601 static int synthetic_nobody_user_build(UserRecord **ret) {
602 return user_record_build(
603 ret,
604 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING(NOBODY_USER_NAME)),
605 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(UID_NOBODY)),
606 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
607 JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
608 JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
609 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
610 }
611
612 int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
613 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
614 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
615 int r;
616
617 if (!valid_user_group_name(name, VALID_USER_RELAX))
618 return -EINVAL;
619
620 r = json_build(&query, JSON_BUILD_OBJECT(
621 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name))));
622 if (r < 0)
623 return r;
624
625 iterator = userdb_iterator_new(LOOKUP_USER, flags);
626 if (!iterator)
627 return -ENOMEM;
628
629 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
630 if (r >= 0) {
631 r = userdb_process(iterator, ret, NULL, NULL, NULL);
632 if (r >= 0)
633 return r;
634 }
635
636 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
637 r = dropin_user_record_by_name(name, NULL, flags, ret);
638 if (r >= 0)
639 return r;
640 }
641
642 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
643 /* Make sure the NSS lookup doesn't recurse back to us. */
644
645 r = userdb_iterator_block_nss_systemd(iterator);
646 if (r >= 0) {
647 /* Client-side NSS fallback */
648 r = nss_user_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
649 if (r >= 0)
650 return r;
651 }
652 }
653
654 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
655 if (streq(name, "root"))
656 return synthetic_root_user_build(ret);
657
658 if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
659 return synthetic_nobody_user_build(ret);
660 }
661
662 return r;
663 }
664
665 int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
666 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
667 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
668 int r;
669
670 if (!uid_is_valid(uid))
671 return -EINVAL;
672
673 r = json_build(&query, JSON_BUILD_OBJECT(
674 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid))));
675 if (r < 0)
676 return r;
677
678 iterator = userdb_iterator_new(LOOKUP_USER, flags);
679 if (!iterator)
680 return -ENOMEM;
681
682 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
683 if (r >= 0) {
684 r = userdb_process(iterator, ret, NULL, NULL, NULL);
685 if (r >= 0)
686 return r;
687 }
688
689 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
690 r = dropin_user_record_by_uid(uid, NULL, flags, ret);
691 if (r >= 0)
692 return r;
693 }
694
695 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
696 r = userdb_iterator_block_nss_systemd(iterator);
697 if (r >= 0) {
698 /* Client-side NSS fallback */
699 r = nss_user_record_by_uid(uid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
700 if (r >= 0)
701 return r;
702 }
703 }
704
705 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
706 if (uid == 0)
707 return synthetic_root_user_build(ret);
708
709 if (uid == UID_NOBODY && synthesize_nobody())
710 return synthetic_nobody_user_build(ret);
711 }
712
713 return r;
714 }
715
716 int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
717 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
718 int r, qr;
719
720 assert(ret);
721
722 iterator = userdb_iterator_new(LOOKUP_USER, flags);
723 if (!iterator)
724 return -ENOMEM;
725
726 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", true, NULL, flags);
727
728 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
729 r = userdb_iterator_block_nss_systemd(iterator);
730 if (r < 0)
731 return r;
732
733 setpwent();
734 iterator->nss_iterating = true;
735 }
736
737 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
738 r = conf_files_list_nulstr(
739 &iterator->dropins,
740 ".user",
741 NULL,
742 CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
743 USERDB_DROPIN_DIR_NULSTR("userdb"));
744 if (r < 0)
745 log_debug_errno(r, "Failed to find user drop-ins, ignoring: %m");
746 }
747
748 /* propagate IPC error, but only if there are no drop-ins */
749 if (qr < 0 &&
750 !iterator->nss_iterating &&
751 strv_isempty(iterator->dropins))
752 return qr;
753
754 *ret = TAKE_PTR(iterator);
755 return 0;
756 }
757
758 int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret) {
759 int r;
760
761 assert(iterator);
762 assert(iterator->what == LOOKUP_USER);
763
764 if (iterator->nss_iterating) {
765 struct passwd *pw;
766
767 /* If NSS isn't covered elsewhere, let's iterate through it first, since it probably contains
768 * the more traditional sources, which are probably good to show first. */
769
770 pw = getpwent();
771 if (pw) {
772 _cleanup_free_ char *buffer = NULL;
773 bool incomplete = false;
774 struct spwd spwd;
775
776 if (streq_ptr(pw->pw_name, "root"))
777 iterator->synthesize_root = false;
778 if (pw->pw_uid == UID_NOBODY)
779 iterator->synthesize_nobody = false;
780
781 if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
782 r = nss_spwd_for_passwd(pw, &spwd, &buffer);
783 if (r < 0) {
784 log_debug_errno(r, "Failed to acquire shadow entry for user %s, ignoring: %m", pw->pw_name);
785 incomplete = ERRNO_IS_PRIVILEGE(r);
786 }
787 } else {
788 r = -EUCLEAN;
789 incomplete = true;
790 }
791
792 r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
793 if (r < 0)
794 return r;
795
796 if (ret)
797 (*ret)->incomplete = incomplete;
798
799 iterator->n_found++;
800 return r;
801 }
802
803 if (errno != 0)
804 log_debug_errno(errno, "Failure to iterate NSS user database, ignoring: %m");
805
806 iterator->nss_iterating = false;
807 endpwent();
808 }
809
810 for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
811 const char *i = iterator->dropins[iterator->current_dropin];
812 _cleanup_free_ char *fn = NULL;
813 uid_t uid;
814 char *e;
815
816 /* Next, let's add in the static drop-ins, which are quick to retrieve */
817
818 r = path_extract_filename(i, &fn);
819 if (r < 0)
820 return r;
821
822 e = endswith(fn, ".user"); /* not actually a .user file? Then skip to next */
823 if (!e)
824 continue;
825
826 *e = 0; /* Chop off suffix */
827
828 if (parse_uid(fn, &uid) < 0) /* not a UID .user file? Then skip to next */
829 continue;
830
831 r = dropin_user_record_by_uid(uid, i, iterator->flags, ret);
832 if (r < 0) {
833 log_debug_errno(r, "Failed to parse user record for UID " UID_FMT ", ignoring: %m", uid);
834 continue; /* If we failed to parse this record, let's suppress it from enumeration,
835 * and continue with the next record. Maybe someone is dropping it files
836 * and only partially wrote this one. */
837 }
838
839 iterator->current_dropin++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
840 iterator->n_found++;
841 return 0;
842 }
843
844 /* Then, let's return the users provided by varlink IPC */
845 r = userdb_process(iterator, ret, NULL, NULL, NULL);
846 if (r < 0) {
847
848 /* Finally, synthesize root + nobody if not done yet */
849 if (iterator->synthesize_root) {
850 iterator->synthesize_root = false;
851 iterator->n_found++;
852 return synthetic_root_user_build(ret);
853 }
854
855 if (iterator->synthesize_nobody) {
856 iterator->synthesize_nobody = false;
857 iterator->n_found++;
858 return synthetic_nobody_user_build(ret);
859 }
860
861 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
862 if (iterator->n_found > 0)
863 return -ESRCH;
864 }
865
866 return r;
867 }
868
869 static int synthetic_root_group_build(GroupRecord **ret) {
870 return group_record_build(
871 ret,
872 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
873 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
874 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
875 }
876
877 static int synthetic_nobody_group_build(GroupRecord **ret) {
878 return group_record_build(
879 ret,
880 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME)),
881 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
882 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
883 }
884
885 int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
886 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
887 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
888 int r;
889
890 if (!valid_user_group_name(name, VALID_USER_RELAX))
891 return -EINVAL;
892
893 r = json_build(&query, JSON_BUILD_OBJECT(
894 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name))));
895 if (r < 0)
896 return r;
897
898 iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
899 if (!iterator)
900 return -ENOMEM;
901
902 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", false, query, flags);
903 if (r >= 0) {
904 r = userdb_process(iterator, NULL, ret, NULL, NULL);
905 if (r >= 0)
906 return r;
907 }
908
909 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
910 r = dropin_group_record_by_name(name, NULL, flags, ret);
911 if (r >= 0)
912 return r;
913 }
914
915
916 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
917 r = userdb_iterator_block_nss_systemd(iterator);
918 if (r >= 0) {
919 r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
920 if (r >= 0)
921 return r;
922 }
923 }
924
925 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
926 if (streq(name, "root"))
927 return synthetic_root_group_build(ret);
928
929 if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
930 return synthetic_nobody_group_build(ret);
931 }
932
933 return r;
934 }
935
936 int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
937 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
938 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
939 int r;
940
941 if (!gid_is_valid(gid))
942 return -EINVAL;
943
944 r = json_build(&query, JSON_BUILD_OBJECT(
945 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid))));
946 if (r < 0)
947 return r;
948
949 iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
950 if (!iterator)
951 return -ENOMEM;
952
953 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", false, query, flags);
954 if (r >= 0) {
955 r = userdb_process(iterator, NULL, ret, NULL, NULL);
956 if (r >= 0)
957 return r;
958 }
959
960 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
961 r = dropin_group_record_by_gid(gid, NULL, flags, ret);
962 if (r >= 0)
963 return r;
964 }
965
966 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
967 r = userdb_iterator_block_nss_systemd(iterator);
968 if (r >= 0) {
969 r = nss_group_record_by_gid(gid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
970 if (r >= 0)
971 return r;
972 }
973 }
974
975 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
976 if (gid == 0)
977 return synthetic_root_group_build(ret);
978
979 if (gid == GID_NOBODY && synthesize_nobody())
980 return synthetic_nobody_group_build(ret);
981 }
982
983 return r;
984 }
985
986 int groupdb_all(UserDBFlags flags, UserDBIterator **ret) {
987 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
988 int r, qr;
989
990 assert(ret);
991
992 iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
993 if (!iterator)
994 return -ENOMEM;
995
996 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", true, NULL, flags);
997
998 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
999 r = userdb_iterator_block_nss_systemd(iterator);
1000 if (r < 0)
1001 return r;
1002
1003 setgrent();
1004 iterator->nss_iterating = true;
1005 }
1006
1007 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
1008 r = conf_files_list_nulstr(
1009 &iterator->dropins,
1010 ".group",
1011 NULL,
1012 CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1013 USERDB_DROPIN_DIR_NULSTR("userdb"));
1014 if (r < 0)
1015 log_debug_errno(r, "Failed to find group drop-ins, ignoring: %m");
1016 }
1017
1018 if (qr < 0 &&
1019 !iterator->nss_iterating &&
1020 strv_isempty(iterator->dropins))
1021 return qr;
1022
1023 *ret = TAKE_PTR(iterator);
1024 return 0;
1025 }
1026
1027 int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret) {
1028 int r;
1029
1030 assert(iterator);
1031 assert(iterator->what == LOOKUP_GROUP);
1032
1033 if (iterator->nss_iterating) {
1034 struct group *gr;
1035
1036 errno = 0;
1037 gr = getgrent();
1038 if (gr) {
1039 _cleanup_free_ char *buffer = NULL;
1040 bool incomplete = false;
1041 struct sgrp sgrp;
1042
1043 if (streq_ptr(gr->gr_name, "root"))
1044 iterator->synthesize_root = false;
1045 if (gr->gr_gid == GID_NOBODY)
1046 iterator->synthesize_nobody = false;
1047
1048 if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
1049 r = nss_sgrp_for_group(gr, &sgrp, &buffer);
1050 if (r < 0) {
1051 log_debug_errno(r, "Failed to acquire shadow entry for group %s, ignoring: %m", gr->gr_name);
1052 incomplete = ERRNO_IS_PRIVILEGE(r);
1053 }
1054 } else {
1055 r = -EUCLEAN;
1056 incomplete = true;
1057 }
1058
1059 r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
1060 if (r < 0)
1061 return r;
1062
1063 if (ret)
1064 (*ret)->incomplete = incomplete;
1065
1066 iterator->n_found++;
1067 return r;
1068 }
1069
1070 if (errno != 0)
1071 log_debug_errno(errno, "Failure to iterate NSS group database, ignoring: %m");
1072
1073 iterator->nss_iterating = false;
1074 endgrent();
1075 }
1076
1077 for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
1078 const char *i = iterator->dropins[iterator->current_dropin];
1079 _cleanup_free_ char *fn = NULL;
1080 gid_t gid;
1081 char *e;
1082
1083 r = path_extract_filename(i, &fn);
1084 if (r < 0)
1085 return r;
1086
1087 e = endswith(fn, ".group");
1088 if (!e)
1089 continue;
1090
1091 *e = 0; /* Chop off suffix */
1092
1093 if (parse_gid(fn, &gid) < 0)
1094 continue;
1095
1096 r = dropin_group_record_by_gid(gid, i, iterator->flags, ret);
1097 if (r < 0) {
1098 log_debug_errno(r, "Failed to parse group record for GID " GID_FMT ", ignoring: %m", gid);
1099 continue;
1100 }
1101
1102 iterator->current_dropin++;
1103 iterator->n_found++;
1104 return 0;
1105 }
1106
1107 r = userdb_process(iterator, NULL, ret, NULL, NULL);
1108 if (r < 0) {
1109 if (iterator->synthesize_root) {
1110 iterator->synthesize_root = false;
1111 iterator->n_found++;
1112 return synthetic_root_group_build(ret);
1113 }
1114
1115 if (iterator->synthesize_nobody) {
1116 iterator->synthesize_nobody = false;
1117 iterator->n_found++;
1118 return synthetic_nobody_group_build(ret);
1119 }
1120
1121 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1122 if (iterator->n_found > 0)
1123 return -ESRCH;
1124 }
1125
1126 return r;
1127 }
1128
1129 static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
1130 int r;
1131
1132 r = conf_files_list_nulstr(
1133 &i->dropins,
1134 ".membership",
1135 NULL,
1136 CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED,
1137 USERDB_DROPIN_DIR_NULSTR("userdb"));
1138 if (r < 0)
1139 log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
1140 }
1141
1142 int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1143 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1144 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
1145 int r, qr;
1146
1147 assert(ret);
1148
1149 if (!valid_user_group_name(name, VALID_USER_RELAX))
1150 return -EINVAL;
1151
1152 r = json_build(&query, JSON_BUILD_OBJECT(
1153 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name))));
1154 if (r < 0)
1155 return r;
1156
1157 iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1158 if (!iterator)
1159 return -ENOMEM;
1160
1161 iterator->filter_user_name = strdup(name);
1162 if (!iterator->filter_user_name)
1163 return -ENOMEM;
1164
1165 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1166
1167 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1168 r = userdb_iterator_block_nss_systemd(iterator);
1169 if (r < 0)
1170 return r;
1171
1172 setgrent();
1173 iterator->nss_iterating = true;
1174 }
1175
1176 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1177 discover_membership_dropins(iterator, flags);
1178
1179 if (qr < 0 &&
1180 !iterator->nss_iterating &&
1181 strv_isempty(iterator->dropins))
1182 return qr;
1183
1184 *ret = TAKE_PTR(iterator);
1185 return 0;
1186 }
1187
1188 int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1189 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1190 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
1191 int r, qr;
1192
1193 assert(ret);
1194
1195 if (!valid_user_group_name(name, VALID_USER_RELAX))
1196 return -EINVAL;
1197
1198 r = json_build(&query, JSON_BUILD_OBJECT(
1199 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name))));
1200 if (r < 0)
1201 return r;
1202
1203 iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1204 if (!iterator)
1205 return -ENOMEM;
1206
1207 iterator->filter_group_name = strdup(name);
1208 if (!iterator->filter_group_name)
1209 return -ENOMEM;
1210
1211 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1212
1213 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1214 _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
1215
1216 r = userdb_iterator_block_nss_systemd(iterator);
1217 if (r < 0)
1218 return r;
1219
1220 /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
1221 (void) nss_group_record_by_name(name, false, &gr);
1222 if (gr) {
1223 iterator->members_of_group = strv_copy(gr->members);
1224 if (!iterator->members_of_group)
1225 return -ENOMEM;
1226
1227 iterator->index_members_of_group = 0;
1228
1229 iterator->found_group_name = strdup(name);
1230 if (!iterator->found_group_name)
1231 return -ENOMEM;
1232 }
1233 }
1234
1235 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1236 discover_membership_dropins(iterator, flags);
1237
1238 if (qr < 0 &&
1239 strv_isempty(iterator->members_of_group) &&
1240 strv_isempty(iterator->dropins))
1241 return qr;
1242
1243 *ret = TAKE_PTR(iterator);
1244 return 0;
1245 }
1246
1247 int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
1248 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1249 int r, qr;
1250
1251 assert(ret);
1252
1253 iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1254 if (!iterator)
1255 return -ENOMEM;
1256
1257 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
1258
1259 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1260 r = userdb_iterator_block_nss_systemd(iterator);
1261 if (r < 0)
1262 return r;
1263
1264 setgrent();
1265 iterator->nss_iterating = true;
1266 }
1267
1268 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1269 discover_membership_dropins(iterator, flags);
1270
1271 if (qr < 0 &&
1272 !iterator->nss_iterating &&
1273 strv_isempty(iterator->dropins))
1274 return qr;
1275
1276 *ret = TAKE_PTR(iterator);
1277 return 0;
1278 }
1279
1280 int membershipdb_iterator_get(
1281 UserDBIterator *iterator,
1282 char **ret_user,
1283 char **ret_group) {
1284
1285 int r;
1286
1287 assert(iterator);
1288
1289 for (;;) {
1290 /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1291 if (!iterator->members_of_group) {
1292 struct group *g;
1293
1294 if (!iterator->nss_iterating)
1295 break;
1296
1297 assert(!iterator->found_user_name);
1298 do {
1299 errno = 0;
1300 g = getgrent();
1301 if (!g) {
1302 if (errno != 0)
1303 log_debug_errno(errno, "Failure during NSS group iteration, ignoring: %m");
1304 break;
1305 }
1306
1307 } while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
1308 strv_isempty(g->gr_mem));
1309
1310 if (g) {
1311 r = free_and_strdup(&iterator->found_group_name, g->gr_name);
1312 if (r < 0)
1313 return r;
1314
1315 if (iterator->filter_user_name)
1316 iterator->members_of_group = strv_new(iterator->filter_user_name);
1317 else
1318 iterator->members_of_group = strv_copy(g->gr_mem);
1319 if (!iterator->members_of_group)
1320 return -ENOMEM;
1321
1322 iterator->index_members_of_group = 0;
1323 } else {
1324 iterator->nss_iterating = false;
1325 endgrent();
1326 break;
1327 }
1328 }
1329
1330 assert(iterator->found_group_name);
1331 assert(iterator->members_of_group);
1332 assert(!iterator->found_user_name);
1333
1334 if (iterator->members_of_group[iterator->index_members_of_group]) {
1335 _cleanup_free_ char *cu = NULL, *cg = NULL;
1336
1337 if (ret_user) {
1338 cu = strdup(iterator->members_of_group[iterator->index_members_of_group]);
1339 if (!cu)
1340 return -ENOMEM;
1341 }
1342
1343 if (ret_group) {
1344 cg = strdup(iterator->found_group_name);
1345 if (!cg)
1346 return -ENOMEM;
1347 }
1348
1349 if (ret_user)
1350 *ret_user = TAKE_PTR(cu);
1351
1352 if (ret_group)
1353 *ret_group = TAKE_PTR(cg);
1354
1355 iterator->index_members_of_group++;
1356 return 0;
1357 }
1358
1359 iterator->members_of_group = strv_free(iterator->members_of_group);
1360 iterator->found_group_name = mfree(iterator->found_group_name);
1361 }
1362
1363 for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
1364 const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
1365 _cleanup_free_ char *un = NULL, *gn = NULL;
1366
1367 e = endswith(i, ".membership");
1368 if (!e)
1369 continue;
1370
1371 c = memchr(i, ':', e - i);
1372 if (!c)
1373 continue;
1374
1375 un = strndup(i, c - i);
1376 if (!un)
1377 return -ENOMEM;
1378 if (iterator->filter_user_name) {
1379 if (!streq(un, iterator->filter_user_name))
1380 continue;
1381 } else if (!valid_user_group_name(un, VALID_USER_RELAX))
1382 continue;
1383
1384 c++; /* skip over ':' */
1385 gn = strndup(c, e - c);
1386 if (!gn)
1387 return -ENOMEM;
1388 if (iterator->filter_group_name) {
1389 if (!streq(gn, iterator->filter_group_name))
1390 continue;
1391 } else if (!valid_user_group_name(gn, VALID_USER_RELAX))
1392 continue;
1393
1394 iterator->current_dropin++;
1395 iterator->n_found++;
1396
1397 if (ret_user)
1398 *ret_user = TAKE_PTR(un);
1399 if (ret_group)
1400 *ret_group = TAKE_PTR(gn);
1401
1402 return 0;
1403 }
1404
1405 r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
1406 if (r < 0 && iterator->n_found > 0)
1407 return -ESRCH;
1408
1409 return r;
1410 }
1411
1412 int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret) {
1413 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1414 _cleanup_strv_free_ char **members = NULL;
1415 int r;
1416
1417 assert(name);
1418 assert(ret);
1419
1420 r = membershipdb_by_group(name, flags, &iterator);
1421 if (r < 0)
1422 return r;
1423
1424 for (;;) {
1425 _cleanup_free_ char *user_name = NULL;
1426
1427 r = membershipdb_iterator_get(iterator, &user_name, NULL);
1428 if (r == -ESRCH)
1429 break;
1430 if (r < 0)
1431 return r;
1432
1433 r = strv_consume(&members, TAKE_PTR(user_name));
1434 if (r < 0)
1435 return r;
1436 }
1437
1438 strv_sort(members);
1439 strv_uniq(members);
1440
1441 *ret = TAKE_PTR(members);
1442 return 0;
1443 }
1444
1445 int userdb_block_nss_systemd(int b) {
1446 _cleanup_(dlclosep) void *dl = NULL;
1447 int (*call)(bool b);
1448
1449 /* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
1450
1451 dl = dlopen(LIBDIR "/libnss_systemd.so.2", RTLD_LAZY|RTLD_NODELETE);
1452 if (!dl) {
1453 /* If the file isn't installed, don't complain loudly */
1454 log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
1455 return 0;
1456 }
1457
1458 call = (int (*)(bool b)) dlsym(dl, "_nss_systemd_block");
1459 if (!call)
1460 /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1461 return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
1462 "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());
1463
1464 return call(b);
1465 }