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