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