]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/userdb.c
Merge pull request #24742 from keszybz/hwdb-252
[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_release(struct user_group_data *d) {
146 json_variant_unref(d->record);
147 }
148
149 static int userdb_on_query_reply(
150 Varlink *link,
151 JsonVariant *parameters,
152 const char *error_id,
153 VarlinkReplyFlags flags,
154 void *userdata) {
155
156 UserDBIterator *iterator = ASSERT_PTR(userdata);
157 int r;
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();
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 const char *e;
407 int r, ret = 0;
408
409 assert(iterator);
410 assert(method);
411
412 if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
413 return -ENOLINK;
414
415 e = getenv("SYSTEMD_BYPASS_USERDB");
416 if (e) {
417 r = parse_boolean(e);
418 if (r > 0)
419 return -ENOLINK;
420 if (r < 0) {
421 except = strv_split(e, ":");
422 if (!except)
423 return -ENOMEM;
424 }
425 }
426
427 e = getenv("SYSTEMD_ONLY_USERDB");
428 if (e) {
429 only = strv_split(e, ":");
430 if (!only)
431 return -ENOMEM;
432 }
433
434 /* First, let's talk to the multiplexer, if we can */
435 if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE)) == 0 &&
436 !strv_contains(except, "io.systemd.Multiplexer") &&
437 (!only || strv_contains(only, "io.systemd.Multiplexer"))) {
438 _cleanup_(json_variant_unrefp) JsonVariant *patched_query = json_variant_ref(query);
439
440 r = json_variant_set_field_string(&patched_query, "service", "io.systemd.Multiplexer");
441 if (r < 0)
442 return log_debug_errno(r, "Unable to set service JSON field: %m");
443
444 r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, patched_query);
445 if (r >= 0) {
446 iterator->nss_covered = true; /* The multiplexer does NSS */
447 iterator->dropin_covered = true; /* It also handles drop-in stuff */
448 return 0;
449 }
450 }
451
452 d = opendir("/run/systemd/userdb/");
453 if (!d) {
454 if (errno == ENOENT)
455 return -ESRCH;
456
457 return -errno;
458 }
459
460 FOREACH_DIRENT(de, d, return -errno) {
461 _cleanup_(json_variant_unrefp) JsonVariant *patched_query = NULL;
462 _cleanup_free_ char *p = NULL;
463 bool is_nss, is_dropin;
464
465 if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
466 continue;
467
468 if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
469 streq(de->d_name, "io.systemd.DynamicUser"))
470 continue;
471
472 /* Avoid NSS is this is requested. Note that we also skip NSS when we were asked to skip the
473 * multiplexer, since in that case it's safer to do NSS in the client side emulation below
474 * (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
475 * anyway). */
476 is_nss = streq(de->d_name, "io.systemd.NameServiceSwitch");
477 if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
478 continue;
479
480 /* Similar for the drop-in service */
481 is_dropin = streq(de->d_name, "io.systemd.DropIn");
482 if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
483 continue;
484
485 if (strv_contains(except, de->d_name))
486 continue;
487
488 if (only && !strv_contains(only, de->d_name))
489 continue;
490
491 p = path_join("/run/systemd/userdb/", de->d_name);
492 if (!p)
493 return -ENOMEM;
494
495 patched_query = json_variant_ref(query);
496 r = json_variant_set_field_string(&patched_query, "service", de->d_name);
497 if (r < 0)
498 return log_debug_errno(r, "Unable to set service JSON field: %m");
499
500 r = userdb_connect(iterator, p, method, more, patched_query);
501 if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
502 * and could connect to it */
503 iterator->nss_covered = true;
504 if (is_dropin && r >= 0)
505 iterator->dropin_covered = true;
506
507 if (ret == 0 && r < 0)
508 ret = r;
509 }
510
511 if (set_isempty(iterator->links))
512 return ret < 0 ? ret : -ESRCH; /* propagate last error we saw if we couldn't connect to anything. */
513
514 /* We connected to some services, in this case, ignore the ones we failed on */
515 return 0;
516 }
517
518 static int userdb_process(
519 UserDBIterator *iterator,
520 UserRecord **ret_user_record,
521 GroupRecord **ret_group_record,
522 char **ret_user_name,
523 char **ret_group_name) {
524
525 int r;
526
527 assert(iterator);
528
529 for (;;) {
530 if (iterator->what == LOOKUP_USER && iterator->found_user) {
531 if (ret_user_record)
532 *ret_user_record = TAKE_PTR(iterator->found_user);
533 else
534 iterator->found_user = user_record_unref(iterator->found_user);
535
536 if (ret_group_record)
537 *ret_group_record = NULL;
538 if (ret_user_name)
539 *ret_user_name = NULL;
540 if (ret_group_name)
541 *ret_group_name = NULL;
542
543 return 0;
544 }
545
546 if (iterator->what == LOOKUP_GROUP && iterator->found_group) {
547 if (ret_group_record)
548 *ret_group_record = TAKE_PTR(iterator->found_group);
549 else
550 iterator->found_group = group_record_unref(iterator->found_group);
551
552 if (ret_user_record)
553 *ret_user_record = NULL;
554 if (ret_user_name)
555 *ret_user_name = NULL;
556 if (ret_group_name)
557 *ret_group_name = NULL;
558
559 return 0;
560 }
561
562 if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
563 if (ret_user_name)
564 *ret_user_name = TAKE_PTR(iterator->found_user_name);
565 else
566 iterator->found_user_name = mfree(iterator->found_user_name);
567
568 if (ret_group_name)
569 *ret_group_name = TAKE_PTR(iterator->found_group_name);
570 else
571 iterator->found_group_name = mfree(iterator->found_group_name);
572
573 if (ret_user_record)
574 *ret_user_record = NULL;
575 if (ret_group_record)
576 *ret_group_record = NULL;
577
578 return 0;
579 }
580
581 if (set_isempty(iterator->links)) {
582 if (iterator->error == 0)
583 return -ESRCH;
584
585 return -abs(iterator->error);
586 }
587
588 if (!iterator->event)
589 return -ESRCH;
590
591 r = sd_event_run(iterator->event, UINT64_MAX);
592 if (r < 0)
593 return r;
594 }
595 }
596
597 static int synthetic_root_user_build(UserRecord **ret) {
598 return user_record_build(
599 ret,
600 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING("root")),
601 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(0)),
602 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
603 JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/root")),
604 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
605 }
606
607 static int synthetic_nobody_user_build(UserRecord **ret) {
608 return user_record_build(
609 ret,
610 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING(NOBODY_USER_NAME)),
611 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(UID_NOBODY)),
612 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
613 JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
614 JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
615 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
616 }
617
618 int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
619 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
620 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
621 int r;
622
623 if (!valid_user_group_name(name, VALID_USER_RELAX))
624 return -EINVAL;
625
626 r = json_build(&query, JSON_BUILD_OBJECT(
627 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name))));
628 if (r < 0)
629 return r;
630
631 iterator = userdb_iterator_new(LOOKUP_USER, flags);
632 if (!iterator)
633 return -ENOMEM;
634
635 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
636 if (r >= 0) {
637 r = userdb_process(iterator, ret, NULL, NULL, NULL);
638 if (r >= 0)
639 return r;
640 }
641
642 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
643 r = dropin_user_record_by_name(name, NULL, flags, ret);
644 if (r >= 0)
645 return r;
646 }
647
648 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
649 /* Make sure the NSS lookup doesn't recurse back to us. */
650
651 r = userdb_iterator_block_nss_systemd(iterator);
652 if (r >= 0) {
653 /* Client-side NSS fallback */
654 r = nss_user_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
655 if (r >= 0)
656 return r;
657 }
658 }
659
660 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
661 if (streq(name, "root"))
662 return synthetic_root_user_build(ret);
663
664 if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
665 return synthetic_nobody_user_build(ret);
666 }
667
668 return r;
669 }
670
671 int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
672 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
673 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
674 int r;
675
676 if (!uid_is_valid(uid))
677 return -EINVAL;
678
679 r = json_build(&query, JSON_BUILD_OBJECT(
680 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid))));
681 if (r < 0)
682 return r;
683
684 iterator = userdb_iterator_new(LOOKUP_USER, flags);
685 if (!iterator)
686 return -ENOMEM;
687
688 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
689 if (r >= 0) {
690 r = userdb_process(iterator, ret, NULL, NULL, NULL);
691 if (r >= 0)
692 return r;
693 }
694
695 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
696 r = dropin_user_record_by_uid(uid, NULL, flags, ret);
697 if (r >= 0)
698 return r;
699 }
700
701 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
702 r = userdb_iterator_block_nss_systemd(iterator);
703 if (r >= 0) {
704 /* Client-side NSS fallback */
705 r = nss_user_record_by_uid(uid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
706 if (r >= 0)
707 return r;
708 }
709 }
710
711 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
712 if (uid == 0)
713 return synthetic_root_user_build(ret);
714
715 if (uid == UID_NOBODY && synthesize_nobody())
716 return synthetic_nobody_user_build(ret);
717 }
718
719 return r;
720 }
721
722 int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
723 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
724 int r, qr;
725
726 assert(ret);
727
728 iterator = userdb_iterator_new(LOOKUP_USER, flags);
729 if (!iterator)
730 return -ENOMEM;
731
732 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", true, NULL, flags);
733
734 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
735 r = userdb_iterator_block_nss_systemd(iterator);
736 if (r < 0)
737 return r;
738
739 setpwent();
740 iterator->nss_iterating = true;
741 }
742
743 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
744 r = conf_files_list_nulstr(
745 &iterator->dropins,
746 ".user",
747 NULL,
748 CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
749 USERDB_DROPIN_DIR_NULSTR("userdb"));
750 if (r < 0)
751 log_debug_errno(r, "Failed to find user drop-ins, ignoring: %m");
752 }
753
754 /* propagate IPC error, but only if there are no drop-ins */
755 if (qr < 0 &&
756 !iterator->nss_iterating &&
757 strv_isempty(iterator->dropins))
758 return qr;
759
760 *ret = TAKE_PTR(iterator);
761 return 0;
762 }
763
764 int userdb_iterator_get(UserDBIterator *iterator, UserRecord **ret) {
765 int r;
766
767 assert(iterator);
768 assert(iterator->what == LOOKUP_USER);
769
770 if (iterator->nss_iterating) {
771 struct passwd *pw;
772
773 /* If NSS isn't covered elsewhere, let's iterate through it first, since it probably contains
774 * the more traditional sources, which are probably good to show first. */
775
776 pw = getpwent();
777 if (pw) {
778 _cleanup_free_ char *buffer = NULL;
779 bool incomplete = false;
780 struct spwd spwd;
781
782 if (streq_ptr(pw->pw_name, "root"))
783 iterator->synthesize_root = false;
784 if (pw->pw_uid == UID_NOBODY)
785 iterator->synthesize_nobody = false;
786
787 if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
788 r = nss_spwd_for_passwd(pw, &spwd, &buffer);
789 if (r < 0) {
790 log_debug_errno(r, "Failed to acquire shadow entry for user %s, ignoring: %m", pw->pw_name);
791 incomplete = ERRNO_IS_PRIVILEGE(r);
792 }
793 } else {
794 r = -EUCLEAN;
795 incomplete = true;
796 }
797
798 r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
799 if (r < 0)
800 return r;
801
802 if (ret)
803 (*ret)->incomplete = incomplete;
804
805 iterator->n_found++;
806 return r;
807 }
808
809 if (errno != 0)
810 log_debug_errno(errno, "Failure to iterate NSS user database, ignoring: %m");
811
812 iterator->nss_iterating = false;
813 endpwent();
814 }
815
816 for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
817 const char *i = iterator->dropins[iterator->current_dropin];
818 _cleanup_free_ char *fn = NULL;
819 uid_t uid;
820 char *e;
821
822 /* Next, let's add in the static drop-ins, which are quick to retrieve */
823
824 r = path_extract_filename(i, &fn);
825 if (r < 0)
826 return r;
827
828 e = endswith(fn, ".user"); /* not actually a .user file? Then skip to next */
829 if (!e)
830 continue;
831
832 *e = 0; /* Chop off suffix */
833
834 if (parse_uid(fn, &uid) < 0) /* not a UID .user file? Then skip to next */
835 continue;
836
837 r = dropin_user_record_by_uid(uid, i, iterator->flags, ret);
838 if (r < 0) {
839 log_debug_errno(r, "Failed to parse user record for UID " UID_FMT ", ignoring: %m", uid);
840 continue; /* If we failed to parse this record, let's suppress it from enumeration,
841 * and continue with the next record. Maybe someone is dropping it files
842 * and only partially wrote this one. */
843 }
844
845 iterator->current_dropin++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
846 iterator->n_found++;
847 return 0;
848 }
849
850 /* Then, let's return the users provided by varlink IPC */
851 r = userdb_process(iterator, ret, NULL, NULL, NULL);
852 if (r < 0) {
853
854 /* Finally, synthesize root + nobody if not done yet */
855 if (iterator->synthesize_root) {
856 iterator->synthesize_root = false;
857 iterator->n_found++;
858 return synthetic_root_user_build(ret);
859 }
860
861 if (iterator->synthesize_nobody) {
862 iterator->synthesize_nobody = false;
863 iterator->n_found++;
864 return synthetic_nobody_user_build(ret);
865 }
866
867 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
868 if (iterator->n_found > 0)
869 return -ESRCH;
870 }
871
872 return r;
873 }
874
875 static int synthetic_root_group_build(GroupRecord **ret) {
876 return group_record_build(
877 ret,
878 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
879 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
880 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
881 }
882
883 static int synthetic_nobody_group_build(GroupRecord **ret) {
884 return group_record_build(
885 ret,
886 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME)),
887 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
888 JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
889 }
890
891 int groupdb_by_name(const char *name, UserDBFlags flags, GroupRecord **ret) {
892 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
893 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
894 int r;
895
896 if (!valid_user_group_name(name, VALID_USER_RELAX))
897 return -EINVAL;
898
899 r = json_build(&query, JSON_BUILD_OBJECT(
900 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name))));
901 if (r < 0)
902 return r;
903
904 iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
905 if (!iterator)
906 return -ENOMEM;
907
908 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", false, query, flags);
909 if (r >= 0) {
910 r = userdb_process(iterator, NULL, ret, NULL, NULL);
911 if (r >= 0)
912 return r;
913 }
914
915 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
916 r = dropin_group_record_by_name(name, NULL, flags, ret);
917 if (r >= 0)
918 return r;
919 }
920
921
922 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
923 r = userdb_iterator_block_nss_systemd(iterator);
924 if (r >= 0) {
925 r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
926 if (r >= 0)
927 return r;
928 }
929 }
930
931 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
932 if (streq(name, "root"))
933 return synthetic_root_group_build(ret);
934
935 if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
936 return synthetic_nobody_group_build(ret);
937 }
938
939 return r;
940 }
941
942 int groupdb_by_gid(gid_t gid, UserDBFlags flags, GroupRecord **ret) {
943 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
944 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
945 int r;
946
947 if (!gid_is_valid(gid))
948 return -EINVAL;
949
950 r = json_build(&query, JSON_BUILD_OBJECT(
951 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid))));
952 if (r < 0)
953 return r;
954
955 iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
956 if (!iterator)
957 return -ENOMEM;
958
959 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", false, query, flags);
960 if (r >= 0) {
961 r = userdb_process(iterator, NULL, ret, NULL, NULL);
962 if (r >= 0)
963 return r;
964 }
965
966 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
967 r = dropin_group_record_by_gid(gid, NULL, flags, ret);
968 if (r >= 0)
969 return r;
970 }
971
972 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
973 r = userdb_iterator_block_nss_systemd(iterator);
974 if (r >= 0) {
975 r = nss_group_record_by_gid(gid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
976 if (r >= 0)
977 return r;
978 }
979 }
980
981 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
982 if (gid == 0)
983 return synthetic_root_group_build(ret);
984
985 if (gid == GID_NOBODY && synthesize_nobody())
986 return synthetic_nobody_group_build(ret);
987 }
988
989 return r;
990 }
991
992 int groupdb_all(UserDBFlags flags, UserDBIterator **ret) {
993 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
994 int r, qr;
995
996 assert(ret);
997
998 iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
999 if (!iterator)
1000 return -ENOMEM;
1001
1002 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", true, NULL, flags);
1003
1004 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1005 r = userdb_iterator_block_nss_systemd(iterator);
1006 if (r < 0)
1007 return r;
1008
1009 setgrent();
1010 iterator->nss_iterating = true;
1011 }
1012
1013 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
1014 r = conf_files_list_nulstr(
1015 &iterator->dropins,
1016 ".group",
1017 NULL,
1018 CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1019 USERDB_DROPIN_DIR_NULSTR("userdb"));
1020 if (r < 0)
1021 log_debug_errno(r, "Failed to find group drop-ins, ignoring: %m");
1022 }
1023
1024 if (qr < 0 &&
1025 !iterator->nss_iterating &&
1026 strv_isempty(iterator->dropins))
1027 return qr;
1028
1029 *ret = TAKE_PTR(iterator);
1030 return 0;
1031 }
1032
1033 int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret) {
1034 int r;
1035
1036 assert(iterator);
1037 assert(iterator->what == LOOKUP_GROUP);
1038
1039 if (iterator->nss_iterating) {
1040 struct group *gr;
1041
1042 errno = 0;
1043 gr = getgrent();
1044 if (gr) {
1045 _cleanup_free_ char *buffer = NULL;
1046 bool incomplete = false;
1047 struct sgrp sgrp;
1048
1049 if (streq_ptr(gr->gr_name, "root"))
1050 iterator->synthesize_root = false;
1051 if (gr->gr_gid == GID_NOBODY)
1052 iterator->synthesize_nobody = false;
1053
1054 if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
1055 r = nss_sgrp_for_group(gr, &sgrp, &buffer);
1056 if (r < 0) {
1057 log_debug_errno(r, "Failed to acquire shadow entry for group %s, ignoring: %m", gr->gr_name);
1058 incomplete = ERRNO_IS_PRIVILEGE(r);
1059 }
1060 } else {
1061 r = -EUCLEAN;
1062 incomplete = true;
1063 }
1064
1065 r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
1066 if (r < 0)
1067 return r;
1068
1069 if (ret)
1070 (*ret)->incomplete = incomplete;
1071
1072 iterator->n_found++;
1073 return r;
1074 }
1075
1076 if (errno != 0)
1077 log_debug_errno(errno, "Failure to iterate NSS group database, ignoring: %m");
1078
1079 iterator->nss_iterating = false;
1080 endgrent();
1081 }
1082
1083 for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
1084 const char *i = iterator->dropins[iterator->current_dropin];
1085 _cleanup_free_ char *fn = NULL;
1086 gid_t gid;
1087 char *e;
1088
1089 r = path_extract_filename(i, &fn);
1090 if (r < 0)
1091 return r;
1092
1093 e = endswith(fn, ".group");
1094 if (!e)
1095 continue;
1096
1097 *e = 0; /* Chop off suffix */
1098
1099 if (parse_gid(fn, &gid) < 0)
1100 continue;
1101
1102 r = dropin_group_record_by_gid(gid, i, iterator->flags, ret);
1103 if (r < 0) {
1104 log_debug_errno(r, "Failed to parse group record for GID " GID_FMT ", ignoring: %m", gid);
1105 continue;
1106 }
1107
1108 iterator->current_dropin++;
1109 iterator->n_found++;
1110 return 0;
1111 }
1112
1113 r = userdb_process(iterator, NULL, ret, NULL, NULL);
1114 if (r < 0) {
1115 if (iterator->synthesize_root) {
1116 iterator->synthesize_root = false;
1117 iterator->n_found++;
1118 return synthetic_root_group_build(ret);
1119 }
1120
1121 if (iterator->synthesize_nobody) {
1122 iterator->synthesize_nobody = false;
1123 iterator->n_found++;
1124 return synthetic_nobody_group_build(ret);
1125 }
1126
1127 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1128 if (iterator->n_found > 0)
1129 return -ESRCH;
1130 }
1131
1132 return r;
1133 }
1134
1135 static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
1136 int r;
1137
1138 r = conf_files_list_nulstr(
1139 &i->dropins,
1140 ".membership",
1141 NULL,
1142 CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED,
1143 USERDB_DROPIN_DIR_NULSTR("userdb"));
1144 if (r < 0)
1145 log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
1146 }
1147
1148 int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1149 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1150 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
1151 int r, qr;
1152
1153 assert(ret);
1154
1155 if (!valid_user_group_name(name, VALID_USER_RELAX))
1156 return -EINVAL;
1157
1158 r = json_build(&query, JSON_BUILD_OBJECT(
1159 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name))));
1160 if (r < 0)
1161 return r;
1162
1163 iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1164 if (!iterator)
1165 return -ENOMEM;
1166
1167 iterator->filter_user_name = strdup(name);
1168 if (!iterator->filter_user_name)
1169 return -ENOMEM;
1170
1171 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1172
1173 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1174 r = userdb_iterator_block_nss_systemd(iterator);
1175 if (r < 0)
1176 return r;
1177
1178 setgrent();
1179 iterator->nss_iterating = true;
1180 }
1181
1182 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1183 discover_membership_dropins(iterator, flags);
1184
1185 if (qr < 0 &&
1186 !iterator->nss_iterating &&
1187 strv_isempty(iterator->dropins))
1188 return qr;
1189
1190 *ret = TAKE_PTR(iterator);
1191 return 0;
1192 }
1193
1194 int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1195 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1196 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
1197 int r, qr;
1198
1199 assert(ret);
1200
1201 if (!valid_user_group_name(name, VALID_USER_RELAX))
1202 return -EINVAL;
1203
1204 r = json_build(&query, JSON_BUILD_OBJECT(
1205 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name))));
1206 if (r < 0)
1207 return r;
1208
1209 iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1210 if (!iterator)
1211 return -ENOMEM;
1212
1213 iterator->filter_group_name = strdup(name);
1214 if (!iterator->filter_group_name)
1215 return -ENOMEM;
1216
1217 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1218
1219 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1220 _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
1221
1222 r = userdb_iterator_block_nss_systemd(iterator);
1223 if (r < 0)
1224 return r;
1225
1226 /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
1227 (void) nss_group_record_by_name(name, false, &gr);
1228 if (gr) {
1229 iterator->members_of_group = strv_copy(gr->members);
1230 if (!iterator->members_of_group)
1231 return -ENOMEM;
1232
1233 iterator->index_members_of_group = 0;
1234
1235 iterator->found_group_name = strdup(name);
1236 if (!iterator->found_group_name)
1237 return -ENOMEM;
1238 }
1239 }
1240
1241 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1242 discover_membership_dropins(iterator, flags);
1243
1244 if (qr < 0 &&
1245 strv_isempty(iterator->members_of_group) &&
1246 strv_isempty(iterator->dropins))
1247 return qr;
1248
1249 *ret = TAKE_PTR(iterator);
1250 return 0;
1251 }
1252
1253 int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
1254 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1255 int r, qr;
1256
1257 assert(ret);
1258
1259 iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1260 if (!iterator)
1261 return -ENOMEM;
1262
1263 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
1264
1265 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1266 r = userdb_iterator_block_nss_systemd(iterator);
1267 if (r < 0)
1268 return r;
1269
1270 setgrent();
1271 iterator->nss_iterating = true;
1272 }
1273
1274 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1275 discover_membership_dropins(iterator, flags);
1276
1277 if (qr < 0 &&
1278 !iterator->nss_iterating &&
1279 strv_isempty(iterator->dropins))
1280 return qr;
1281
1282 *ret = TAKE_PTR(iterator);
1283 return 0;
1284 }
1285
1286 int membershipdb_iterator_get(
1287 UserDBIterator *iterator,
1288 char **ret_user,
1289 char **ret_group) {
1290
1291 int r;
1292
1293 assert(iterator);
1294
1295 for (;;) {
1296 /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1297 if (!iterator->members_of_group) {
1298 struct group *g;
1299
1300 if (!iterator->nss_iterating)
1301 break;
1302
1303 assert(!iterator->found_user_name);
1304 do {
1305 errno = 0;
1306 g = getgrent();
1307 if (!g) {
1308 if (errno != 0)
1309 log_debug_errno(errno, "Failure during NSS group iteration, ignoring: %m");
1310 break;
1311 }
1312
1313 } while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
1314 strv_isempty(g->gr_mem));
1315
1316 if (g) {
1317 r = free_and_strdup(&iterator->found_group_name, g->gr_name);
1318 if (r < 0)
1319 return r;
1320
1321 if (iterator->filter_user_name)
1322 iterator->members_of_group = strv_new(iterator->filter_user_name);
1323 else
1324 iterator->members_of_group = strv_copy(g->gr_mem);
1325 if (!iterator->members_of_group)
1326 return -ENOMEM;
1327
1328 iterator->index_members_of_group = 0;
1329 } else {
1330 iterator->nss_iterating = false;
1331 endgrent();
1332 break;
1333 }
1334 }
1335
1336 assert(iterator->found_group_name);
1337 assert(iterator->members_of_group);
1338 assert(!iterator->found_user_name);
1339
1340 if (iterator->members_of_group[iterator->index_members_of_group]) {
1341 _cleanup_free_ char *cu = NULL, *cg = NULL;
1342
1343 if (ret_user) {
1344 cu = strdup(iterator->members_of_group[iterator->index_members_of_group]);
1345 if (!cu)
1346 return -ENOMEM;
1347 }
1348
1349 if (ret_group) {
1350 cg = strdup(iterator->found_group_name);
1351 if (!cg)
1352 return -ENOMEM;
1353 }
1354
1355 if (ret_user)
1356 *ret_user = TAKE_PTR(cu);
1357
1358 if (ret_group)
1359 *ret_group = TAKE_PTR(cg);
1360
1361 iterator->index_members_of_group++;
1362 return 0;
1363 }
1364
1365 iterator->members_of_group = strv_free(iterator->members_of_group);
1366 iterator->found_group_name = mfree(iterator->found_group_name);
1367 }
1368
1369 for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
1370 const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
1371 _cleanup_free_ char *un = NULL, *gn = NULL;
1372
1373 e = endswith(i, ".membership");
1374 if (!e)
1375 continue;
1376
1377 c = memchr(i, ':', e - i);
1378 if (!c)
1379 continue;
1380
1381 un = strndup(i, c - i);
1382 if (!un)
1383 return -ENOMEM;
1384 if (iterator->filter_user_name) {
1385 if (!streq(un, iterator->filter_user_name))
1386 continue;
1387 } else if (!valid_user_group_name(un, VALID_USER_RELAX))
1388 continue;
1389
1390 c++; /* skip over ':' */
1391 gn = strndup(c, e - c);
1392 if (!gn)
1393 return -ENOMEM;
1394 if (iterator->filter_group_name) {
1395 if (!streq(gn, iterator->filter_group_name))
1396 continue;
1397 } else if (!valid_user_group_name(gn, VALID_USER_RELAX))
1398 continue;
1399
1400 iterator->current_dropin++;
1401 iterator->n_found++;
1402
1403 if (ret_user)
1404 *ret_user = TAKE_PTR(un);
1405 if (ret_group)
1406 *ret_group = TAKE_PTR(gn);
1407
1408 return 0;
1409 }
1410
1411 r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
1412 if (r < 0 && iterator->n_found > 0)
1413 return -ESRCH;
1414
1415 return r;
1416 }
1417
1418 int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret) {
1419 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1420 _cleanup_strv_free_ char **members = NULL;
1421 int r;
1422
1423 assert(name);
1424 assert(ret);
1425
1426 r = membershipdb_by_group(name, flags, &iterator);
1427 if (r < 0)
1428 return r;
1429
1430 for (;;) {
1431 _cleanup_free_ char *user_name = NULL;
1432
1433 r = membershipdb_iterator_get(iterator, &user_name, NULL);
1434 if (r == -ESRCH)
1435 break;
1436 if (r < 0)
1437 return r;
1438
1439 r = strv_consume(&members, TAKE_PTR(user_name));
1440 if (r < 0)
1441 return r;
1442 }
1443
1444 strv_sort(members);
1445 strv_uniq(members);
1446
1447 *ret = TAKE_PTR(members);
1448 return 0;
1449 }
1450
1451 int userdb_block_nss_systemd(int b) {
1452 _cleanup_(dlclosep) void *dl = NULL;
1453 int (*call)(bool b);
1454
1455 /* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
1456
1457 dl = dlopen(ROOTLIBDIR "/libnss_systemd.so.2", RTLD_LAZY|RTLD_NODELETE);
1458 if (!dl) {
1459 /* If the file isn't installed, don't complain loudly */
1460 log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
1461 return 0;
1462 }
1463
1464 call = (int (*)(bool b)) dlsym(dl, "_nss_systemd_block");
1465 if (!call)
1466 /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1467 return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
1468 "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());
1469
1470 return call(b);
1471 }