]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/userdb.c
userdb: initialize .synthesize_root/.synthesize_nobody in generic code
[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:
93 assert_not_reached("Unexpected state?");
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:
336 assert_not_reached("unexpected lookup");
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;
408 struct dirent *de;
409 const char *e;
410 int r, ret = 0;
411
412 assert(iterator);
413 assert(method);
414
b2148254
LP
415 if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
416 return -ENOLINK;
417
ec8e4a0e
LP
418 e = getenv("SYSTEMD_BYPASS_USERDB");
419 if (e) {
420 r = parse_boolean(e);
421 if (r > 0)
422 return -ENOLINK;
423 if (r < 0) {
424 except = strv_split(e, ":");
425 if (!except)
426 return -ENOMEM;
427 }
428 }
429
430 e = getenv("SYSTEMD_ONLY_USERDB");
431 if (e) {
432 only = strv_split(e, ":");
433 if (!only)
434 return -ENOMEM;
435 }
436
437 /* First, let's talk to the multiplexer, if we can */
85f088ab 438 if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE)) == 0 &&
ec8e4a0e
LP
439 !strv_contains(except, "io.systemd.Multiplexer") &&
440 (!only || strv_contains(only, "io.systemd.Multiplexer"))) {
441 _cleanup_(json_variant_unrefp) JsonVariant *patched_query = json_variant_ref(query);
442
443 r = json_variant_set_field_string(&patched_query, "service", "io.systemd.Multiplexer");
444 if (r < 0)
445 return log_debug_errno(r, "Unable to set service JSON field: %m");
446
447 r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, patched_query);
448 if (r >= 0) {
449 iterator->nss_covered = true; /* The multiplexer does NSS */
85f088ab 450 iterator->dropin_covered = true; /* It also handles drop-in stuff */
ec8e4a0e
LP
451 return 0;
452 }
453 }
454
455 d = opendir("/run/systemd/userdb/");
456 if (!d) {
457 if (errno == ENOENT)
458 return -ESRCH;
459
460 return -errno;
461 }
462
463 FOREACH_DIRENT(de, d, return -errno) {
464 _cleanup_(json_variant_unrefp) JsonVariant *patched_query = NULL;
465 _cleanup_free_ char *p = NULL;
85f088ab 466 bool is_nss, is_dropin;
ec8e4a0e
LP
467
468 if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
469 continue;
470
80d88a82 471 if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
ec8e4a0e
LP
472 streq(de->d_name, "io.systemd.DynamicUser"))
473 continue;
474
475 /* Avoid NSS is this is requested. Note that we also skip NSS when we were asked to skip the
476 * multiplexer, since in that case it's safer to do NSS in the client side emulation below
477 * (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
478 * anyway). */
479 is_nss = streq(de->d_name, "io.systemd.NameServiceSwitch");
80d88a82 480 if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
ec8e4a0e
LP
481 continue;
482
85f088ab
LP
483 /* Similar for the drop-in service */
484 is_dropin = streq(de->d_name, "io.systemd.DropIn");
485 if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
486 continue;
487
ec8e4a0e
LP
488 if (strv_contains(except, de->d_name))
489 continue;
490
491 if (only && !strv_contains(only, de->d_name))
492 continue;
493
494 p = path_join("/run/systemd/userdb/", de->d_name);
495 if (!p)
496 return -ENOMEM;
497
498 patched_query = json_variant_ref(query);
499 r = json_variant_set_field_string(&patched_query, "service", de->d_name);
500 if (r < 0)
501 return log_debug_errno(r, "Unable to set service JSON field: %m");
502
503 r = userdb_connect(iterator, p, method, more, patched_query);
85f088ab
LP
504 if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
505 * and could connect to it */
ec8e4a0e 506 iterator->nss_covered = true;
85f088ab
LP
507 if (is_dropin && r >= 0)
508 iterator->dropin_covered = true;
ec8e4a0e
LP
509
510 if (ret == 0 && r < 0)
511 ret = r;
512 }
513
514 if (set_isempty(iterator->links))
58dbf4c9 515 return ret < 0 ? ret : -ESRCH; /* propagate last error we saw if we couldn't connect to anything. */
ec8e4a0e
LP
516
517 /* We connected to some services, in this case, ignore the ones we failed on */
518 return 0;
519}
520
521static int userdb_process(
522 UserDBIterator *iterator,
523 UserRecord **ret_user_record,
524 GroupRecord **ret_group_record,
525 char **ret_user_name,
526 char **ret_group_name) {
527
528 int r;
529
530 assert(iterator);
531
532 for (;;) {
533 if (iterator->what == LOOKUP_USER && iterator->found_user) {
534 if (ret_user_record)
535 *ret_user_record = TAKE_PTR(iterator->found_user);
536 else
537 iterator->found_user = user_record_unref(iterator->found_user);
538
539 if (ret_group_record)
540 *ret_group_record = NULL;
541 if (ret_user_name)
542 *ret_user_name = NULL;
543 if (ret_group_name)
544 *ret_group_name = NULL;
545
546 return 0;
547 }
548
549 if (iterator->what == LOOKUP_GROUP && iterator->found_group) {
550 if (ret_group_record)
551 *ret_group_record = TAKE_PTR(iterator->found_group);
552 else
553 iterator->found_group = group_record_unref(iterator->found_group);
554
555 if (ret_user_record)
556 *ret_user_record = NULL;
557 if (ret_user_name)
558 *ret_user_name = NULL;
559 if (ret_group_name)
560 *ret_group_name = NULL;
561
562 return 0;
563 }
564
565 if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
566 if (ret_user_name)
567 *ret_user_name = TAKE_PTR(iterator->found_user_name);
568 else
569 iterator->found_user_name = mfree(iterator->found_user_name);
570
571 if (ret_group_name)
572 *ret_group_name = TAKE_PTR(iterator->found_group_name);
573 else
574 iterator->found_group_name = mfree(iterator->found_group_name);
575
576 if (ret_user_record)
577 *ret_user_record = NULL;
578 if (ret_group_record)
579 *ret_group_record = NULL;
580
581 return 0;
582 }
583
584 if (set_isempty(iterator->links)) {
585 if (iterator->error == 0)
586 return -ESRCH;
587
588 return -abs(iterator->error);
589 }
590
591 if (!iterator->event)
592 return -ESRCH;
593
594 r = sd_event_run(iterator->event, UINT64_MAX);
595 if (r < 0)
596 return r;
597 }
598}
599
600static int synthetic_root_user_build(UserRecord **ret) {
601 return user_record_build(
602 ret,
603 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING("root")),
604 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(0)),
605 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(0)),
606 JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_STRING("/root")),
607 JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
608}
609
610static int synthetic_nobody_user_build(UserRecord **ret) {
611 return user_record_build(
612 ret,
613 JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(NOBODY_USER_NAME)),
614 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(UID_NOBODY)),
615 JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)),
616 JSON_BUILD_PAIR("shell", JSON_BUILD_STRING(NOLOGIN)),
617 JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
618 JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("intrinsic"))));
619}
620
621int userdb_by_name(const char *name, UserDBFlags flags, UserRecord **ret) {
622 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
623 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
624 int r;
625
7a8867ab 626 if (!valid_user_group_name(name, VALID_USER_RELAX))
ec8e4a0e
LP
627 return -EINVAL;
628
629 r = json_build(&query, JSON_BUILD_OBJECT(
630 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name))));
631 if (r < 0)
632 return r;
633
7c674191 634 iterator = userdb_iterator_new(LOOKUP_USER, flags);
ec8e4a0e
LP
635 if (!iterator)
636 return -ENOMEM;
637
638 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
639 if (r >= 0) {
640 r = userdb_process(iterator, ret, NULL, NULL, NULL);
641 if (r >= 0)
642 return r;
643 }
644
85f088ab
LP
645 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
646 r = dropin_user_record_by_name(name, NULL, flags, ret);
647 if (r >= 0)
648 return r;
649 }
650
80d88a82 651 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
037b0a47 652 /* Make sure the NSS lookup doesn't recurse back to us. */
ec8e4a0e 653
037b0a47
LP
654 r = userdb_iterator_block_nss_systemd(iterator);
655 if (r >= 0) {
ec8e4a0e 656 /* Client-side NSS fallback */
80d88a82 657 r = nss_user_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
ec8e4a0e
LP
658 if (r >= 0)
659 return r;
660 }
661 }
662
663 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
664 if (streq(name, "root"))
665 return synthetic_root_user_build(ret);
666
667 if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
668 return synthetic_nobody_user_build(ret);
669 }
670
671 return r;
672}
673
674int userdb_by_uid(uid_t uid, UserDBFlags flags, UserRecord **ret) {
675 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
676 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
677 int r;
678
679 if (!uid_is_valid(uid))
680 return -EINVAL;
681
682 r = json_build(&query, JSON_BUILD_OBJECT(
683 JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid))));
684 if (r < 0)
685 return r;
686
7c674191 687 iterator = userdb_iterator_new(LOOKUP_USER, flags);
ec8e4a0e
LP
688 if (!iterator)
689 return -ENOMEM;
690
691 r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", false, query, flags);
692 if (r >= 0) {
693 r = userdb_process(iterator, ret, NULL, NULL, NULL);
694 if (r >= 0)
695 return r;
696 }
697
85f088ab
LP
698 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
699 r = dropin_user_record_by_uid(uid, NULL, flags, ret);
700 if (r >= 0)
701 return r;
702 }
703
80d88a82 704 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
037b0a47
LP
705 r = userdb_iterator_block_nss_systemd(iterator);
706 if (r >= 0) {
ec8e4a0e 707 /* Client-side NSS fallback */
80d88a82 708 r = nss_user_record_by_uid(uid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
ec8e4a0e
LP
709 if (r >= 0)
710 return r;
711 }
712 }
713
714 if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE)) {
715 if (uid == 0)
716 return synthetic_root_user_build(ret);
717
718 if (uid == UID_NOBODY && synthesize_nobody())
719 return synthetic_nobody_user_build(ret);
720 }
721
722 return r;
723}
724
725int userdb_all(UserDBFlags flags, UserDBIterator **ret) {
726 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
85f088ab 727 int r, qr;
ec8e4a0e
LP
728
729 assert(ret);
730
7c674191 731 iterator = userdb_iterator_new(LOOKUP_USER, flags);
ec8e4a0e
LP
732 if (!iterator)
733 return -ENOMEM;
734
85f088ab 735 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", true, NULL, flags);
ec8e4a0e 736
85f088ab 737 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
037b0a47
LP
738 r = userdb_iterator_block_nss_systemd(iterator);
739 if (r < 0)
740 return r;
ec8e4a0e
LP
741
742 setpwent();
743 iterator->nss_iterating = true;
85f088ab
LP
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;
ec8e4a0e 762
ec8e4a0e
LP
763 *ret = TAKE_PTR(iterator);
764 return 0;
765}
766
767int 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
80d88a82 790 if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
7c674191
LP
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;
ec8e4a0e
LP
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;
27a5a22f
LP
807
808 iterator->n_found++;
ec8e4a0e
LP
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
85f088ab
LP
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;
ec8e4a0e 824
85f088ab
LP
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);
ec8e4a0e 855 if (r < 0) {
85f088ab
LP
856
857 /* Finally, synthesize root + nobody if not done yet */
ec8e4a0e
LP
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 }
ec8e4a0e 869
77fe7d15
LP
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 }
ec8e4a0e
LP
874
875 return r;
876}
877
878static 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
886static 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
894int 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
7a8867ab 899 if (!valid_user_group_name(name, VALID_USER_RELAX))
ec8e4a0e
LP
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
7c674191 907 iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
ec8e4a0e
LP
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
85f088ab
LP
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
80d88a82 925 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
037b0a47
LP
926 r = userdb_iterator_block_nss_systemd(iterator);
927 if (r >= 0) {
80d88a82 928 r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
ec8e4a0e
LP
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
945int 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
7c674191 958 iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
ec8e4a0e
LP
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
85f088ab
LP
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
80d88a82 975 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
037b0a47
LP
976 r = userdb_iterator_block_nss_systemd(iterator);
977 if (r >= 0) {
80d88a82 978 r = nss_group_record_by_gid(gid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
ec8e4a0e
LP
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
995int groupdb_all(UserDBFlags flags, UserDBIterator **ret) {
996 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
85f088ab 997 int r, qr;
ec8e4a0e
LP
998
999 assert(ret);
1000
7c674191 1001 iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
ec8e4a0e
LP
1002 if (!iterator)
1003 return -ENOMEM;
1004
85f088ab 1005 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", true, NULL, flags);
ec8e4a0e 1006
85f088ab 1007 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
037b0a47
LP
1008 r = userdb_iterator_block_nss_systemd(iterator);
1009 if (r < 0)
1010 return r;
ec8e4a0e
LP
1011
1012 setgrent();
1013 iterator->nss_iterating = true;
85f088ab
LP
1014 }
1015
1016 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
1017 r = conf_files_list_nulstr(
1018 &iterator->dropins,
1019 ".group",
1020 NULL,
1021 CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1022 USERDB_DROPIN_DIR_NULSTR("userdb"));
1023 if (r < 0)
1024 log_debug_errno(r, "Failed to find group drop-ins, ignoring: %m");
1025 }
1026
1027 if (qr < 0 &&
1028 !iterator->nss_iterating &&
1029 strv_isempty(iterator->dropins))
1030 return qr;
ec8e4a0e 1031
ec8e4a0e
LP
1032 *ret = TAKE_PTR(iterator);
1033 return 0;
1034}
1035
1036int groupdb_iterator_get(UserDBIterator *iterator, GroupRecord **ret) {
1037 int r;
1038
1039 assert(iterator);
1040 assert(iterator->what == LOOKUP_GROUP);
1041
1042 if (iterator->nss_iterating) {
1043 struct group *gr;
1044
1045 errno = 0;
1046 gr = getgrent();
1047 if (gr) {
1048 _cleanup_free_ char *buffer = NULL;
1049 bool incomplete = false;
1050 struct sgrp sgrp;
1051
1052 if (streq_ptr(gr->gr_name, "root"))
1053 iterator->synthesize_root = false;
1054 if (gr->gr_gid == GID_NOBODY)
1055 iterator->synthesize_nobody = false;
1056
80d88a82 1057 if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
7c674191
LP
1058 r = nss_sgrp_for_group(gr, &sgrp, &buffer);
1059 if (r < 0) {
1060 log_debug_errno(r, "Failed to acquire shadow entry for group %s, ignoring: %m", gr->gr_name);
1061 incomplete = ERRNO_IS_PRIVILEGE(r);
1062 }
1063 } else {
1064 r = -EUCLEAN;
1065 incomplete = true;
ec8e4a0e
LP
1066 }
1067
1068 r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
1069 if (r < 0)
1070 return r;
1071
1072 if (ret)
1073 (*ret)->incomplete = incomplete;
27a5a22f
LP
1074
1075 iterator->n_found++;
ec8e4a0e
LP
1076 return r;
1077 }
1078
1079 if (errno != 0)
1080 log_debug_errno(errno, "Failure to iterate NSS group database, ignoring: %m");
1081
1082 iterator->nss_iterating = false;
1083 endgrent();
1084 }
1085
85f088ab
LP
1086 for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
1087 const char *i = iterator->dropins[iterator->current_dropin];
1088 _cleanup_free_ char *fn = NULL;
1089 gid_t gid;
1090 char *e;
1091
1092 r = path_extract_filename(i, &fn);
1093 if (r < 0)
1094 return r;
1095
1096 e = endswith(fn, ".group");
1097 if (!e)
1098 continue;
1099
1100 *e = 0; /* Chop off suffix */
1101
1102 if (parse_gid(fn, &gid) < 0)
1103 continue;
1104
1105 r = dropin_group_record_by_gid(gid, i, iterator->flags, ret);
1106 if (r < 0) {
1107 log_debug_errno(r, "Failed to parse group record for GID " GID_FMT ", ignoring: %m", gid);
1108 continue;
1109 }
1110
1111 iterator->current_dropin++;
1112 iterator->n_found++;
1113 return 0;
1114 }
1115
ec8e4a0e
LP
1116 r = userdb_process(iterator, NULL, ret, NULL, NULL);
1117 if (r < 0) {
1118 if (iterator->synthesize_root) {
1119 iterator->synthesize_root = false;
1120 iterator->n_found++;
1121 return synthetic_root_group_build(ret);
1122 }
1123
1124 if (iterator->synthesize_nobody) {
1125 iterator->synthesize_nobody = false;
1126 iterator->n_found++;
1127 return synthetic_nobody_group_build(ret);
1128 }
ec8e4a0e 1129
77fe7d15
LP
1130 /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1131 if (iterator->n_found > 0)
1132 return -ESRCH;
1133 }
ec8e4a0e
LP
1134
1135 return r;
1136}
1137
85f088ab
LP
1138static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
1139 int r;
1140
1141 r = conf_files_list_nulstr(
1142 &i->dropins,
1143 ".membership",
1144 NULL,
1145 CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED,
1146 USERDB_DROPIN_DIR_NULSTR("userdb"));
1147 if (r < 0)
1148 log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
1149}
1150
ec8e4a0e
LP
1151int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1152 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1153 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
85f088ab 1154 int r, qr;
ec8e4a0e
LP
1155
1156 assert(ret);
1157
7a8867ab 1158 if (!valid_user_group_name(name, VALID_USER_RELAX))
ec8e4a0e
LP
1159 return -EINVAL;
1160
1161 r = json_build(&query, JSON_BUILD_OBJECT(
1162 JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name))));
1163 if (r < 0)
1164 return r;
1165
7c674191 1166 iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
ec8e4a0e
LP
1167 if (!iterator)
1168 return -ENOMEM;
1169
ec8e4a0e
LP
1170 iterator->filter_user_name = strdup(name);
1171 if (!iterator->filter_user_name)
1172 return -ENOMEM;
1173
85f088ab
LP
1174 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1175
1176 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1177 r = userdb_iterator_block_nss_systemd(iterator);
1178 if (r < 0)
1179 return r;
ec8e4a0e 1180
85f088ab
LP
1181 setgrent();
1182 iterator->nss_iterating = true;
1183 }
ec8e4a0e 1184
85f088ab
LP
1185 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1186 discover_membership_dropins(iterator, flags);
1187
1188 if (qr < 0 &&
1189 !iterator->nss_iterating &&
1190 strv_isempty(iterator->dropins))
1191 return qr;
1192
1193 *ret = TAKE_PTR(iterator);
1194 return 0;
ec8e4a0e
LP
1195}
1196
1197int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1198 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1199 _cleanup_(json_variant_unrefp) JsonVariant *query = NULL;
85f088ab 1200 int r, qr;
ec8e4a0e
LP
1201
1202 assert(ret);
1203
7a8867ab 1204 if (!valid_user_group_name(name, VALID_USER_RELAX))
ec8e4a0e
LP
1205 return -EINVAL;
1206
1207 r = json_build(&query, JSON_BUILD_OBJECT(
1208 JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name))));
1209 if (r < 0)
1210 return r;
1211
7c674191 1212 iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
ec8e4a0e
LP
1213 if (!iterator)
1214 return -ENOMEM;
1215
85f088ab
LP
1216 iterator->filter_group_name = strdup(name);
1217 if (!iterator->filter_group_name)
1218 return -ENOMEM;
ec8e4a0e 1219
85f088ab 1220 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
ec8e4a0e 1221
85f088ab
LP
1222 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1223 _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
ec8e4a0e 1224
85f088ab
LP
1225 r = userdb_iterator_block_nss_systemd(iterator);
1226 if (r < 0)
1227 return r;
ec8e4a0e 1228
85f088ab
LP
1229 /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
1230 (void) nss_group_record_by_name(name, false, &gr);
1231 if (gr) {
1232 iterator->members_of_group = strv_copy(gr->members);
1233 if (!iterator->members_of_group)
1234 return -ENOMEM;
1235
1236 iterator->index_members_of_group = 0;
1237
1238 iterator->found_group_name = strdup(name);
1239 if (!iterator->found_group_name)
1240 return -ENOMEM;
1241 }
ec8e4a0e
LP
1242 }
1243
85f088ab
LP
1244 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1245 discover_membership_dropins(iterator, flags);
ec8e4a0e 1246
85f088ab
LP
1247 if (qr < 0 &&
1248 strv_isempty(iterator->members_of_group) &&
1249 strv_isempty(iterator->dropins))
1250 return qr;
ec8e4a0e 1251
85f088ab
LP
1252 *ret = TAKE_PTR(iterator);
1253 return 0;
ec8e4a0e
LP
1254}
1255
1256int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
1257 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
85f088ab 1258 int r, qr;
ec8e4a0e
LP
1259
1260 assert(ret);
1261
7c674191 1262 iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
ec8e4a0e
LP
1263 if (!iterator)
1264 return -ENOMEM;
1265
85f088ab 1266 qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
ec8e4a0e 1267
85f088ab
LP
1268 if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1269 r = userdb_iterator_block_nss_systemd(iterator);
1270 if (r < 0)
1271 return r;
ec8e4a0e 1272
85f088ab
LP
1273 setgrent();
1274 iterator->nss_iterating = true;
1275 }
ec8e4a0e 1276
85f088ab
LP
1277 if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1278 discover_membership_dropins(iterator, flags);
ec8e4a0e 1279
85f088ab
LP
1280 if (qr < 0 &&
1281 !iterator->nss_iterating &&
1282 strv_isempty(iterator->dropins))
1283 return qr;
ec8e4a0e 1284
85f088ab
LP
1285 *ret = TAKE_PTR(iterator);
1286 return 0;
ec8e4a0e
LP
1287}
1288
1289int membershipdb_iterator_get(
1290 UserDBIterator *iterator,
1291 char **ret_user,
1292 char **ret_group) {
1293
1294 int r;
1295
1296 assert(iterator);
1297
1298 for (;;) {
d9f5f2a1 1299 /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
ec8e4a0e
LP
1300 if (!iterator->members_of_group) {
1301 struct group *g;
1302
1303 if (!iterator->nss_iterating)
1304 break;
1305
1306 assert(!iterator->found_user_name);
1307 do {
1308 errno = 0;
1309 g = getgrent();
1310 if (!g) {
1311 if (errno != 0)
1312 log_debug_errno(errno, "Failure during NSS group iteration, ignoring: %m");
1313 break;
1314 }
1315
1316 } while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
1317 strv_isempty(g->gr_mem));
1318
1319 if (g) {
1320 r = free_and_strdup(&iterator->found_group_name, g->gr_name);
1321 if (r < 0)
1322 return r;
1323
1324 if (iterator->filter_user_name)
1325 iterator->members_of_group = strv_new(iterator->filter_user_name);
1326 else
1327 iterator->members_of_group = strv_copy(g->gr_mem);
1328 if (!iterator->members_of_group)
1329 return -ENOMEM;
1330
1331 iterator->index_members_of_group = 0;
1332 } else {
1333 iterator->nss_iterating = false;
1334 endgrent();
1335 break;
1336 }
1337 }
1338
1339 assert(iterator->found_group_name);
1340 assert(iterator->members_of_group);
1341 assert(!iterator->found_user_name);
1342
1343 if (iterator->members_of_group[iterator->index_members_of_group]) {
1344 _cleanup_free_ char *cu = NULL, *cg = NULL;
1345
1346 if (ret_user) {
1347 cu = strdup(iterator->members_of_group[iterator->index_members_of_group]);
1348 if (!cu)
1349 return -ENOMEM;
1350 }
1351
1352 if (ret_group) {
1353 cg = strdup(iterator->found_group_name);
1354 if (!cg)
1355 return -ENOMEM;
1356 }
1357
1358 if (ret_user)
1359 *ret_user = TAKE_PTR(cu);
1360
1361 if (ret_group)
1362 *ret_group = TAKE_PTR(cg);
1363
1364 iterator->index_members_of_group++;
1365 return 0;
1366 }
1367
1368 iterator->members_of_group = strv_free(iterator->members_of_group);
1369 iterator->found_group_name = mfree(iterator->found_group_name);
1370 }
1371
85f088ab
LP
1372 for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
1373 const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
1374 _cleanup_free_ char *un = NULL, *gn = NULL;
1375
1376 e = endswith(i, ".membership");
1377 if (!e)
1378 continue;
1379
1380 c = memchr(i, ':', e - i);
1381 if (!c)
1382 continue;
1383
1384 un = strndup(i, c - i);
1385 if (!un)
1386 return -ENOMEM;
1387 if (iterator->filter_user_name) {
1388 if (!streq(un, iterator->filter_user_name))
1389 continue;
1390 } else if (!valid_user_group_name(un, VALID_USER_RELAX))
1391 continue;
1392
1393 c++; /* skip over ':' */
1394 gn = strndup(c, e - c);
1395 if (!gn)
1396 return -ENOMEM;
1397 if (iterator->filter_group_name) {
1398 if (!streq(gn, iterator->filter_group_name))
1399 continue;
1400 } else if (!valid_user_group_name(gn, VALID_USER_RELAX))
1401 continue;
1402
1403 iterator->current_dropin++;
1404 iterator->n_found++;
1405
1406 if (ret_user)
1407 *ret_user = TAKE_PTR(un);
1408 if (ret_group)
1409 *ret_group = TAKE_PTR(gn);
1410
1411 return 0;
1412 }
1413
ec8e4a0e
LP
1414 r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
1415 if (r < 0 && iterator->n_found > 0)
1416 return -ESRCH;
1417
1418 return r;
1419}
1420
1421int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret) {
1422 _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
1423 _cleanup_strv_free_ char **members = NULL;
1424 int r;
1425
1426 assert(name);
1427 assert(ret);
1428
1429 r = membershipdb_by_group(name, flags, &iterator);
1430 if (r < 0)
1431 return r;
1432
1433 for (;;) {
1434 _cleanup_free_ char *user_name = NULL;
1435
1436 r = membershipdb_iterator_get(iterator, &user_name, NULL);
1437 if (r == -ESRCH)
1438 break;
1439 if (r < 0)
1440 return r;
1441
1442 r = strv_consume(&members, TAKE_PTR(user_name));
1443 if (r < 0)
1444 return r;
1445 }
1446
1447 strv_sort(members);
1448 strv_uniq(members);
1449
1450 *ret = TAKE_PTR(members);
1451 return 0;
1452}
1453
037b0a47
LP
1454int userdb_block_nss_systemd(int b) {
1455 _cleanup_(dlclosep) void *dl = NULL;
1456 int (*call)(bool b);
ec8e4a0e 1457
037b0a47 1458 /* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
ec8e4a0e 1459
a127c620 1460 dl = dlopen(ROOTLIBDIR "/libnss_systemd.so.2", RTLD_LAZY|RTLD_NODELETE);
037b0a47
LP
1461 if (!dl) {
1462 /* If the file isn't installed, don't complain loudly */
1463 log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
1464 return 0;
ec8e4a0e
LP
1465 }
1466
037b0a47
LP
1467 call = (int (*)(bool b)) dlsym(dl, "_nss_systemd_block");
1468 if (!call)
fa027117 1469 /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
037b0a47
LP
1470 return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
1471 "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());
ec8e4a0e 1472
037b0a47 1473 return call(b);
ec8e4a0e 1474}