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