• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 14564590592

20 Apr 2025 07:18PM UTC coverage: 72.107% (+0.006%) from 72.101%
14564590592

push

github

yuwata
man/sd-bus: Add at least one reference per sd-bus function man page

Some sd-bus man pages did not have any references on the main
 sd-bus man page. Unless you accidentally stumbled on them from
other pages  it was difficult to discover them.

296903 of 411754 relevant lines covered (72.11%)

688508.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.17
/src/shared/userdb.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <sys/auxv.h>
4

5
#include "sd-varlink.h"
6

7
#include "bitfield.h"
8
#include "conf-files.h"
9
#include "dirent-util.h"
10
#include "dlfcn-util.h"
11
#include "errno-util.h"
12
#include "fd-util.h"
13
#include "format-util.h"
14
#include "json-util.h"
15
#include "log.h"
16
#include "missing_syscall.h"
17
#include "parse-util.h"
18
#include "set.h"
19
#include "socket-util.h"
20
#include "strv.h"
21
#include "uid-classification.h"
22
#include "user-record-nss.h"
23
#include "user-util.h"
24
#include "userdb.h"
25
#include "userdb-dropin.h"
26

27
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, sd_varlink, sd_varlink_unref);
1,178✔
28

29
typedef enum LookupWhat {
30
        LOOKUP_USER,
31
        LOOKUP_GROUP,
32
        LOOKUP_MEMBERSHIP,
33
        _LOOKUP_WHAT_MAX,
34
} LookupWhat;
35

36
struct UserDBIterator {
37
        LookupWhat what;
38
        UserDBFlags flags;
39
        Set *links;
40

41
        const char *method; /* Note, this is a const static string! */
42
        sd_json_variant *query;
43

44
        bool more:1;
45
        bool nss_covered:1;
46
        bool nss_iterating:1;
47
        bool dropin_covered:1;
48
        bool synthesize_root:1;
49
        bool synthesize_nobody:1;
50
        bool nss_systemd_blocked:1;
51

52
        char **dropins;
53
        size_t current_dropin;
54
        int error;
55
        unsigned n_found;
56

57
        sd_event *event;
58
        UserRecord *found_user;                   /* when .what == LOOKUP_USER */
59
        GroupRecord *found_group;                 /* when .what == LOOKUP_GROUP */
60

61
        char *found_user_name, *found_group_name; /* when .what == LOOKUP_MEMBERSHIP */
62
        char **members_of_group;
63
        size_t index_members_of_group;
64
        char *filter_user_name, *filter_group_name;
65
};
66

67
static int userdb_connect(UserDBIterator *iterator, const char *path, const char *method, bool more, sd_json_variant *query);
68

69
UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
64,817✔
70
        if (!iterator)
64,817✔
71
                return NULL;
72

73
        sd_json_variant_unref(iterator->query);
64,661✔
74

75
        set_free(iterator->links);
64,661✔
76
        strv_free(iterator->dropins);
64,661✔
77

78
        switch (iterator->what) {
64,661✔
79

80
        case LOOKUP_USER:
4,719✔
81
                user_record_unref(iterator->found_user);
4,719✔
82

83
                if (iterator->nss_iterating)
4,719✔
84
                        endpwent();
×
85

86
                break;
87

88
        case LOOKUP_GROUP:
29,656✔
89
                group_record_unref(iterator->found_group);
29,656✔
90

91
                if (iterator->nss_iterating)
29,656✔
92
                        endgrent();
×
93

94
                break;
95

96
        case LOOKUP_MEMBERSHIP:
30,286✔
97
                free(iterator->found_user_name);
30,286✔
98
                free(iterator->found_group_name);
30,286✔
99
                strv_free(iterator->members_of_group);
30,286✔
100
                free(iterator->filter_user_name);
30,286✔
101
                free(iterator->filter_group_name);
30,286✔
102

103
                if (iterator->nss_iterating)
30,286✔
104
                        endgrent();
×
105

106
                break;
107

108
        default:
×
109
                assert_not_reached();
×
110
        }
111

112
        sd_event_unref(iterator->event);
64,661✔
113

114
        if (iterator->nss_systemd_blocked)
64,661✔
115
                assert_se(userdb_block_nss_systemd(false) >= 0);
858✔
116

117
        return mfree(iterator);
64,661✔
118
}
119

120
static UserDBIterator* userdb_iterator_new(LookupWhat what, UserDBFlags flags) {
64,661✔
121
        UserDBIterator *i;
64,661✔
122

123
        assert(what >= 0);
64,661✔
124
        assert(what < _LOOKUP_WHAT_MAX);
64,661✔
125

126
        i = new(UserDBIterator, 1);
64,661✔
127
        if (!i)
64,661✔
128
                return NULL;
129

130
        *i = (UserDBIterator) {
64,661✔
131
                .what = what,
132
                .flags = flags,
133
                .synthesize_root = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC),
64,661✔
134
                .synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC),
135
        };
136

137
        return i;
64,661✔
138
}
139

140
static int userdb_iterator_block_nss_systemd(UserDBIterator *iterator) {
858✔
141
        int r;
858✔
142

143
        assert(iterator);
858✔
144

145
        if (iterator->nss_systemd_blocked)
858✔
146
                return 0;
147

148
        r = userdb_block_nss_systemd(true);
858✔
149
        if (r < 0)
858✔
150
                return r;
151

152
        iterator->nss_systemd_blocked = true;
858✔
153
        return 1;
858✔
154
}
155

156
struct user_group_data {
157
        sd_json_variant *record;
158
        bool incomplete;
159
};
160

161
static void user_group_data_done(struct user_group_data *d) {
2,824✔
162
        sd_json_variant_unref(d->record);
2,824✔
163
}
2,824✔
164

165
struct membership_data {
166
        char *user_name;
167
        char *group_name;
168
};
169

170
static void membership_data_done(struct membership_data *d) {
35✔
171
        free(d->user_name);
35✔
172
        free(d->group_name);
35✔
173
}
35✔
174

175
static int userdb_maybe_restart_query(
109,633✔
176
                UserDBIterator *iterator,
177
                sd_varlink *link,
178
                sd_json_variant *parameters,
179
                const char *error_id) {
180

181
        int r;
109,633✔
182

183
        assert(iterator);
109,633✔
184
        assert(link);
109,633✔
185
        assert(error_id);
109,633✔
186

187
        /* These fields were added in v258 and didn't exist in previous implementations. Hence, we consider
188
         * their support optional: if any service refuses any of these fields, we'll restart the query
189
         * without them, and apply the filtering they are supposed to do client side. */
190
        static const char *const fields[] = {
191
                "fuzzyNames",
192
                "dispositionMask",
193
                "uidMin",
194
                "uidMax",
195
                "gidMin",
196
                "gidMax",
197
                NULL
198
        };
199

200
        /* Figure out if the reported error indicates any of the suppressible fields are at fault, and that
201
         * our query actually included them */
202
        bool restart = false;
767,257✔
203
        STRV_FOREACH(f, fields) {
767,257✔
204
                if (!sd_varlink_error_is_invalid_parameter(error_id, parameters, *f))
657,660✔
205
                        continue;
657,624✔
206

207
                if (!sd_json_variant_by_key(iterator->query, *f))
36✔
208
                        continue;
×
209

210
                restart = true;
211
                break;
212
        }
213

214
        if (!restart)
109,633✔
215
                return 0;
109,633✔
216

217
        /* Now patch the fields out */
218
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query =
109,633✔
219
                sd_json_variant_ref(iterator->query);
36✔
220

221
        r = sd_json_variant_filter(&patched_query, (char**const) fields);
36✔
222
        if (r < 0)
36✔
223
                return r;
224

225
        /* NB: we stored the socket path in the varlink connection description when we set things up here! */
226
        r = userdb_connect(
108✔
227
                        iterator,
228
                        ASSERT_PTR(sd_varlink_get_description(link)),
36✔
229
                        iterator->method,
230
                        iterator->more,
36✔
231
                        patched_query);
232
        if (r < 0)
36✔
233
                return r;
234

235
        log_debug("Restarted query to service '%s' due to missing features.", sd_varlink_get_description(link));
36✔
236
        return 1;
237
}
238

239
static int userdb_on_query_reply(
112,492✔
240
                sd_varlink *link,
241
                sd_json_variant *parameters,
242
                const char *error_id,
243
                sd_varlink_reply_flags_t flags,
244
                void *userdata) {
245

246
        UserDBIterator *iterator = ASSERT_PTR(userdata);
112,492✔
247
        int r;
112,492✔
248

249
        if (error_id) {
112,492✔
250
                log_debug("Got lookup error: %s", error_id);
109,633✔
251

252
                r = userdb_maybe_restart_query(iterator, link, parameters, error_id);
109,633✔
253
                if (r < 0)
109,633✔
254
                        return r;
255
                if (r > 0) {
109,633✔
256
                        r = 0;
36✔
257
                        goto finish;
36✔
258
                }
259

260
                /* Convert various forms of record not found into -ESRCH, since NSS typically doesn't care,
261
                 * about the details. Note that if a userName specification is refused as invalid parameter,
262
                 * we also turn this into -ESRCH following the logic that there cannot be a user record for a
263
                 * completely invalid user name. */
264
                if (STR_IN_SET(error_id,
109,597✔
265
                               "io.systemd.UserDatabase.NoRecordFound",
266
                               "io.systemd.UserDatabase.ConflictingRecordFound") ||
30✔
267
                    sd_varlink_error_is_invalid_parameter(error_id, parameters, "userName") ||
60✔
268
                    sd_varlink_error_is_invalid_parameter(error_id, parameters, "groupName"))
30✔
269
                        r = -ESRCH;
109,567✔
270
                else if (streq(error_id, "io.systemd.UserDatabase.NonMatchingRecordFound"))
30✔
271
                        r = -ENOEXEC;
272
                else if (streq(error_id, "io.systemd.UserDatabase.ServiceNotAvailable"))
30✔
273
                        r = -EHOSTDOWN;
274
                else if (streq(error_id, "io.systemd.UserDatabase.EnumerationNotSupported"))
30✔
275
                        r = -EOPNOTSUPP;
276
                else if (streq(error_id, SD_VARLINK_ERROR_TIMEOUT))
×
277
                        r = -ETIMEDOUT;
278
                else
279
                        r = -EIO;
×
280

281
                goto finish;
109,597✔
282
        }
283

284
        switch (iterator->what) {
2,859✔
285

286
        case LOOKUP_USER: {
2,253✔
287
                _cleanup_(user_group_data_done) struct user_group_data user_data = {};
2,253✔
288

289
                static const sd_json_dispatch_field dispatch_table[] = {
2,253✔
290
                        { "record",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
291
                        { "incomplete", SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool, offsetof(struct user_group_data, incomplete), 0 },
292
                        {}
293
                };
294
                _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
1,955✔
295

296
                assert_se(!iterator->found_user);
2,253✔
297

298
                r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &user_data);
2,253✔
299
                if (r < 0)
2,253✔
300
                        goto finish;
×
301

302
                if (!user_data.record) {
2,253✔
303
                        r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
×
304
                        goto finish;
×
305
                }
306

307
                hr = user_record_new();
2,253✔
308
                if (!hr) {
2,253✔
309
                        r = -ENOMEM;
×
310
                        goto finish;
×
311
                }
312

313
                r = user_record_load(hr, user_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
2,253✔
314
                if (r < 0)
2,253✔
315
                        goto finish;
×
316

317
                if (!hr->service) {
2,253✔
318
                        r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "User record does not carry service information, refusing.");
×
319
                        goto finish;
×
320
                }
321

322
                hr->incomplete = user_data.incomplete;
2,253✔
323

324
                /* We match the root user by the name since the name is our primary key. We match the nobody
325
                 * use by UID though, since the name might differ on OSes */
326
                if (streq_ptr(hr->user_name, "root"))
2,253✔
327
                        iterator->synthesize_root = false;
491✔
328
                if (hr->uid == UID_NOBODY)
2,253✔
329
                        iterator->synthesize_nobody = false;
15✔
330

331
                iterator->found_user = TAKE_PTR(hr);
2,253✔
332
                iterator->n_found++;
2,253✔
333

334
                /* More stuff coming? then let's just exit cleanly here */
335
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
2,253✔
336
                        return 0;
298✔
337

338
                /* Otherwise, let's remove this link and exit cleanly then */
339
                r = 0;
1,955✔
340
                goto finish;
1,955✔
341
        }
342

343
        case LOOKUP_GROUP: {
571✔
344
                _cleanup_(user_group_data_done) struct user_group_data group_data = {};
571✔
345

346
                static const sd_json_dispatch_field dispatch_table[] = {
571✔
347
                        { "record",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
348
                        { "incomplete", SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool, offsetof(struct user_group_data, incomplete), 0 },
349
                        {}
350
                };
351
                _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
346✔
352

353
                assert_se(!iterator->found_group);
571✔
354

355
                r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &group_data);
571✔
356
                if (r < 0)
571✔
357
                        goto finish;
×
358

359
                if (!group_data.record) {
571✔
360
                        r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
×
361
                        goto finish;
×
362
                }
363

364
                g = group_record_new();
571✔
365
                if (!g) {
571✔
366
                        r = -ENOMEM;
×
367
                        goto finish;
×
368
                }
369

370
                r = group_record_load(g, group_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
571✔
371
                if (r < 0)
571✔
372
                        goto finish;
×
373

374
                if (!g->service) {
571✔
375
                        r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Group record does not carry service information, refusing.");
×
376
                        goto finish;
×
377
                }
378

379
                g->incomplete = group_data.incomplete;
571✔
380

381
                if (streq_ptr(g->group_name, "root"))
571✔
382
                        iterator->synthesize_root = false;
20✔
383
                if (g->gid == GID_NOBODY)
571✔
384
                        iterator->synthesize_nobody = false;
11✔
385

386
                iterator->found_group = TAKE_PTR(g);
571✔
387
                iterator->n_found++;
571✔
388

389
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
571✔
390
                        return 0;
225✔
391

392
                r = 0;
346✔
393
                goto finish;
346✔
394
        }
395

396
        case LOOKUP_MEMBERSHIP: {
35✔
397
                _cleanup_(membership_data_done) struct membership_data membership_data = {};
25✔
398

399
                static const sd_json_dispatch_field dispatch_table[] = {
35✔
400
                        { "userName",  SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, user_name),  SD_JSON_RELAX },
401
                        { "groupName", SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, group_name), SD_JSON_RELAX },
402
                        {}
403
                };
404

405
                assert(!iterator->found_user_name);
35✔
406
                assert(!iterator->found_group_name);
35✔
407

408
                r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &membership_data);
35✔
409
                if (r < 0)
35✔
410
                        goto finish;
×
411

412
                iterator->found_user_name = TAKE_PTR(membership_data.user_name);
35✔
413
                iterator->found_group_name = TAKE_PTR(membership_data.group_name);
35✔
414
                iterator->n_found++;
35✔
415

416
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
35✔
417
                        return 0;
10✔
418

419
                r = 0;
25✔
420
                goto finish;
25✔
421
        }
422

423
        default:
×
424
                assert_not_reached();
×
425
        }
426

427
finish:
2,326✔
428
        /* If we got one ESRCH or ENOEXEC, let that win. This way when we do a wild dump we won't be tripped
429
         * up by bad errors – as long as at least one connection ended somewhat cleanly */
430
        if (IN_SET(r, -ESRCH, -ENOEXEC) || iterator->error == 0)
111,959✔
431
                iterator->error = -r;
110,910✔
432

433
        assert_se(set_remove(iterator->links, link) == link);
111,959✔
434
        link = sd_varlink_unref(link);
111,959✔
435
        return 0;
111,959✔
436
}
437

438
static int userdb_connect(
113,145✔
439
                UserDBIterator *iterator,
440
                const char *path,
441
                const char *method,
442
                bool more,
443
                sd_json_variant *query) {
444

445
        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
113,145✔
446
        int r;
113,145✔
447

448
        assert(iterator);
113,145✔
449
        assert(path);
113,145✔
450
        assert(method);
113,145✔
451

452
        r = sd_varlink_connect_address(&vl, path);
113,145✔
453
        if (r < 0)
113,145✔
454
                return log_debug_errno(r, "Unable to connect to %s: %m", path);
8✔
455

456
        sd_varlink_set_userdata(vl, iterator);
113,137✔
457

458
        if (!iterator->event) {
113,137✔
459
                r = sd_event_new(&iterator->event);
36,948✔
460
                if (r < 0)
36,948✔
461
                        return log_debug_errno(r, "Unable to allocate event loop: %m");
×
462
        }
463

464
        r = sd_varlink_attach_event(vl, iterator->event, SD_EVENT_PRIORITY_NORMAL);
113,137✔
465
        if (r < 0)
113,137✔
466
                return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m");
×
467

468
        /* Note, this is load bearing: we store the socket path as description for the varlink
469
         * connection. That's not just good for debugging, but we reuse this information in case we need to
470
         * reissue the query with a reduced set of parameters. */
471
        r = sd_varlink_set_description(vl, path);
113,137✔
472
        if (r < 0)
113,137✔
473
                return log_debug_errno(r, "Failed to set varlink connection description: %m");
×
474

475
        r = sd_varlink_bind_reply(vl, userdb_on_query_reply);
113,137✔
476
        if (r < 0)
113,137✔
477
                return log_debug_errno(r, "Failed to bind reply callback: %m");
×
478

479
        _cleanup_free_ char *service = NULL;
113,137✔
480
        r = path_extract_filename(path, &service);
113,137✔
481
        if (r < 0)
113,137✔
482
                return log_debug_errno(r, "Failed to extract service name from socket path: %m");
×
483
        assert(r != O_DIRECTORY);
113,137✔
484

485
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query = sd_json_variant_ref(query);
226,274✔
486
        r = sd_json_variant_set_field_string(&patched_query, "service", service);
113,137✔
487
        if (r < 0)
113,137✔
488
                return log_debug_errno(r, "Unable to set service JSON field: %m");
×
489

490
        if (more)
113,137✔
491
                r = sd_varlink_observe(vl, method, patched_query);
52,281✔
492
        else
493
                r = sd_varlink_invoke(vl, method, patched_query);
60,856✔
494
        if (r < 0)
113,137✔
495
                return log_debug_errno(r, "Failed to invoke varlink method: %m");
×
496

497
        r = set_ensure_consume(&iterator->links, &link_hash_ops, TAKE_PTR(vl));
113,137✔
498
        if (r < 0)
113,137✔
499
                return log_debug_errno(r, "Failed to add varlink connection to set: %m");
×
500
        return r;
501
}
502

503
static int userdb_start_query(
64,661✔
504
                UserDBIterator *iterator,
505
                const char *method, /* must be a static string, we are not going to copy this here! */
506
                bool more,
507
                sd_json_variant *query,
508
                UserDBFlags flags) {
509

510
        _cleanup_strv_free_ char **except = NULL, **only = NULL;
64,661✔
511
        _cleanup_closedir_ DIR *d = NULL;
64,661✔
512
        const char *e;
64,661✔
513
        int r, ret = 0;
64,661✔
514

515
        assert(iterator);
64,661✔
516
        assert(method);
64,661✔
517

518
        if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
64,661✔
519
                return -ENOLINK;
520

521
        assert(!iterator->query);
36,956✔
522
        iterator->method = method; /* note: we don't make a copy here! */
36,956✔
523
        iterator->query = sd_json_variant_ref(query);
36,956✔
524
        iterator->more = more;
36,956✔
525

526
        e = getenv("SYSTEMD_BYPASS_USERDB");
36,956✔
527
        if (e) {
36,956✔
528
                r = parse_boolean(e);
1,299✔
529
                if (r > 0)
1,299✔
530
                        return -ENOLINK;
531
                if (r < 0) {
1,299✔
532
                        except = strv_split(e, ":");
1,299✔
533
                        if (!except)
1,299✔
534
                                return -ENOMEM;
535
                }
536
        }
537

538
        e = getenv("SYSTEMD_ONLY_USERDB");
36,956✔
539
        if (e) {
36,956✔
540
                only = strv_split(e, ":");
×
541
                if (!only)
×
542
                        return -ENOMEM;
543
        }
544

545
        /* First, let's talk to the multiplexer, if we can */
546
        if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN)) == 0 &&
38,748✔
547
            !strv_contains(except, "io.systemd.Multiplexer") &&
1,792✔
548
            (!only || strv_contains(only, "io.systemd.Multiplexer"))) {
1,792✔
549
                r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, query);
1,792✔
550
                if (r >= 0) {
1,792✔
551
                        iterator->nss_covered = true; /* The multiplexer does NSS */
1,792✔
552
                        iterator->dropin_covered = true; /* It also handles drop-in stuff */
1,792✔
553
                        return 0;
1,792✔
554
                }
555
        }
556

557
        d = opendir("/run/systemd/userdb/");
35,164✔
558
        if (!d) {
35,164✔
559
                if (errno == ENOENT)
×
560
                        return -ESRCH;
561

562
                return -errno;
×
563
        }
564

565
        FOREACH_DIRENT(de, d, return -errno) {
288,414✔
566
                _cleanup_free_ char *p = NULL;
111,317✔
567
                bool is_nss, is_dropin;
182,922✔
568

569
                if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
182,922✔
570
                        continue;
35,156✔
571

572
                if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
147,766✔
573
                    streq(de->d_name, "io.systemd.DynamicUser"))
30✔
574
                        continue;
6✔
575

576
                /* Avoid NSS if this is requested. Note that we also skip NSS when we were asked to skip the
577
                 * multiplexer, since in that case it's safer to do NSS in the client side emulation below
578
                 * (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
579
                 * anyway). */
580
                is_nss = streq(de->d_name, "io.systemd.NameServiceSwitch");
147,760✔
581
                if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
147,760✔
582
                        continue;
35,124✔
583

584
                /* Similar for the drop-in service */
585
                is_dropin = streq(de->d_name, "io.systemd.DropIn");
112,636✔
586
                if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
112,636✔
587
                        continue;
1,215✔
588

589
                if (strv_contains(except, de->d_name))
111,421✔
590
                        continue;
104✔
591

592
                if (only && !strv_contains(only, de->d_name))
111,317✔
593
                        continue;
×
594

595
                p = path_join("/run/systemd/userdb/", de->d_name);
111,317✔
596
                if (!p)
111,317✔
597
                        return -ENOMEM;
×
598

599
                r = userdb_connect(iterator, p, method, more, query);
111,317✔
600
                if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
111,317✔
601
                                       * and could connect to it */
602
                        iterator->nss_covered = true;
32✔
603
                if (is_dropin && r >= 0)
111,317✔
604
                        iterator->dropin_covered = true;
33,941✔
605

606
                if (ret == 0 && r < 0)
111,317✔
607
                        ret = r;
8✔
608
        }
609

610
        if (set_isempty(iterator->links))
35,164✔
611
                return ret < 0 ? ret : -ESRCH; /* propagate last error we saw if we couldn't connect to anything. */
8✔
612

613
        /* We connected to some services, in this case, ignore the ones we failed on */
614
        return 0;
615
}
616

617
static int userdb_process(
37,555✔
618
                UserDBIterator *iterator,
619
                UserRecord **ret_user_record,
620
                GroupRecord **ret_group_record,
621
                char **ret_user_name,
622
                char **ret_group_name) {
623

624
        int r;
37,555✔
625

626
        assert(iterator);
37,555✔
627

628
        for (;;) {
578,981✔
629
                if (iterator->what == LOOKUP_USER && iterator->found_user) {
578,981✔
630
                        if (ret_user_record)
2,253✔
631
                                *ret_user_record = TAKE_PTR(iterator->found_user);
2,253✔
632
                        else
633
                                iterator->found_user = user_record_unref(iterator->found_user);
×
634

635
                        if (ret_group_record)
2,253✔
636
                                *ret_group_record = NULL;
×
637
                        if (ret_user_name)
2,253✔
638
                                *ret_user_name = NULL;
×
639
                        if (ret_group_name)
2,253✔
640
                                *ret_group_name = NULL;
×
641

642
                        return 0;
2,253✔
643
                }
644

645
                if (iterator->what == LOOKUP_GROUP && iterator->found_group) {
576,728✔
646
                        if (ret_group_record)
571✔
647
                                *ret_group_record = TAKE_PTR(iterator->found_group);
571✔
648
                        else
649
                                iterator->found_group = group_record_unref(iterator->found_group);
×
650

651
                        if (ret_user_record)
571✔
652
                                *ret_user_record = NULL;
×
653
                        if (ret_user_name)
571✔
654
                                *ret_user_name = NULL;
×
655
                        if (ret_group_name)
571✔
656
                                *ret_group_name = NULL;
×
657

658
                        return 0;
571✔
659
                }
660

661
                if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
576,157✔
662
                        if (ret_user_name)
35✔
663
                                *ret_user_name = TAKE_PTR(iterator->found_user_name);
21✔
664
                        else
665
                                iterator->found_user_name = mfree(iterator->found_user_name);
14✔
666

667
                        if (ret_group_name)
35✔
668
                                *ret_group_name = TAKE_PTR(iterator->found_group_name);
28✔
669
                        else
670
                                iterator->found_group_name = mfree(iterator->found_group_name);
7✔
671

672
                        if (ret_user_record)
35✔
673
                                *ret_user_record = NULL;
×
674
                        if (ret_group_record)
35✔
675
                                *ret_group_record = NULL;
×
676

677
                        return 0;
35✔
678
                }
679

680
                if (set_isempty(iterator->links)) {
576,122✔
681
                        if (iterator->error == 0)
34,696✔
682
                                return -ESRCH;
683

684
                        return -abs(iterator->error);
34,639✔
685
                }
686

687
                if (!iterator->event)
541,426✔
688
                        return -ESRCH;
689

690
                r = sd_event_run(iterator->event, UINT64_MAX);
541,426✔
691
                if (r < 0)
541,426✔
692
                        return r;
693
        }
694
}
695

696
static int synthetic_root_user_build(UserRecord **ret) {
7✔
697
        return user_record_build(
14✔
698
                        ret,
699
                        SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING("root")),
7✔
700
                                          SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(0)),
701
                                          SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(0)),
702
                                          SD_JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/root")),
703
                                          SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
704
}
705

706
static int synthetic_nobody_user_build(UserRecord **ret) {
6✔
707
        return user_record_build(
12✔
708
                        ret,
709
                        SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING(NOBODY_USER_NAME)),
6✔
710
                                          SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(UID_NOBODY)),
711
                                          SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(GID_NOBODY)),
712
                                          SD_JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
713
                                          SD_JSON_BUILD_PAIR("locked", SD_JSON_BUILD_BOOLEAN(true)),
714
                                          SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
715
}
716

717
static int synthetic_foreign_user_build(uid_t foreign_uid, UserRecord **ret) {
5✔
718
        assert(ret);
5✔
719

720
        if (!uid_is_valid(foreign_uid))
5✔
721
                return -ESRCH;
5✔
722
        if (foreign_uid > 0xFFFF)
4✔
723
                return -ESRCH;
724

725
        _cleanup_free_ char *un = NULL;
4✔
726
        if (asprintf(&un, "foreign-" UID_FMT, foreign_uid) < 0)
4✔
727
                return -ENOMEM;
728

729
        _cleanup_free_ char *rn = NULL;
4✔
730
        if (asprintf(&rn, "Foreign System Image UID " UID_FMT, foreign_uid) < 0)
4✔
731
                return -ENOMEM;
732

733
        return user_record_build(
4✔
734
                        ret,
735
                        SD_JSON_BUILD_OBJECT(
4✔
736
                                        SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(un)),
737
                                        SD_JSON_BUILD_PAIR("realName", SD_JSON_BUILD_STRING(rn)),
738
                                        SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_uid)),
739
                                        SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_uid)),
740
                                        SD_JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
741
                                        SD_JSON_BUILD_PAIR("locked", SD_JSON_BUILD_BOOLEAN(true)),
742
                                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign"))));
743
}
744

745
static int user_name_foreign_extract_uid(const char *name, uid_t *ret_uid) {
14,681✔
746
        int r;
14,681✔
747

748
        assert(name);
14,681✔
749
        assert(ret_uid);
14,681✔
750

751
        /* Parses the inner UID from a user name of the foreign UID range, in the form "foreign-NNN". Returns
752
         * > 0 if that worked, 0 if it didn't. */
753

754
        const char *e = startswith(name, "foreign-");
14,681✔
755
        if (!e)
14,681✔
756
                goto nomatch;
14,677✔
757

758
        uid_t uid;
4✔
759
        r = parse_uid(e, &uid);
4✔
760
        if (r < 0)
4✔
761
                goto nomatch;
1✔
762

763
        if (uid > 0xFFFF)
3✔
764
                goto nomatch;
2✔
765

766
        *ret_uid = uid;
1✔
767
        return 1;
1✔
768

769
nomatch:
14,680✔
770
        *ret_uid = UID_INVALID;
14,680✔
771
        return 0;
14,680✔
772
}
773

774
static int query_append_disposition_mask(sd_json_variant **query, uint64_t mask) {
25✔
775
        int r;
25✔
776

777
        assert(query);
25✔
778

779
        if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL))
25✔
780
                return 0;
25✔
781

782
        _cleanup_strv_free_ char **dispositions = NULL;
17✔
783
        for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
136✔
784
                if (!BITS_SET(mask, d))
238✔
785
                        continue;
94✔
786

787
                r = strv_extend(&dispositions, user_disposition_to_string(d));
25✔
788
                if (r < 0)
25✔
789
                        return r;
790
        }
791

792
        return sd_json_variant_merge_objectbo(
17✔
793
                        query,
794
                        SD_JSON_BUILD_PAIR_STRV("dispositionMask", dispositions));
795
}
796

797
static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *match) {
4,719✔
798
        int r;
4,719✔
799

800
        assert(query);
4,719✔
801

802
        if (!userdb_match_is_set(match))
4,719✔
803
                return 0;
804

805
        r = sd_json_variant_merge_objectbo(
15✔
806
                        query,
807
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
808
                        SD_JSON_BUILD_PAIR_CONDITION(match->uid_min > 0, "uidMin", SD_JSON_BUILD_UNSIGNED(match->uid_min)),
809
                        SD_JSON_BUILD_PAIR_CONDITION(match->uid_max < UID_INVALID-1, "uidMax", SD_JSON_BUILD_UNSIGNED(match->uid_max)));
810
        if (r < 0)
13✔
811
                return r;
812

813
        return query_append_disposition_mask(query, match->disposition_mask);
13✔
814
}
815

816
static int userdb_by_name_fallbacks(
1,804✔
817
                const char *name,
818
                UserDBIterator *iterator,
819
                UserDBFlags flags,
820
                UserRecord **ret) {
821
        int r;
1,804✔
822

823
        assert(name);
1,804✔
824
        assert(iterator);
1,804✔
825
        assert(ret);
1,804✔
826

827
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
1,804✔
828
                r = dropin_user_record_by_name(name, /* path= */ NULL, flags, ret);
1,633✔
829
                if (r >= 0)
1,633✔
830
                        return r;
831
        }
832

833
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
717✔
834
                /* Make sure the NSS lookup doesn't recurse back to us. */
835

836
                r = userdb_iterator_block_nss_systemd(iterator);
220✔
837
                if (r >= 0) {
220✔
838
                        /* Client-side NSS fallback */
839
                        r = nss_user_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
220✔
840
                        if (r >= 0)
220✔
841
                                return r;
842
                }
843
        }
844

845
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
598✔
846
                if (streq(name, "root"))
261✔
847
                        return synthetic_root_user_build(ret);
1✔
848

849
                if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
260✔
850
                        return synthetic_nobody_user_build(ret);
×
851
        }
852

853
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
597✔
854
                uid_t foreign_uid;
260✔
855
                r = user_name_foreign_extract_uid(name, &foreign_uid);
260✔
856
                if (r < 0)
260✔
857
                        return r;
1✔
858
                if (r > 0)
260✔
859
                        return synthetic_foreign_user_build(foreign_uid, ret);
1✔
860
        }
861

862
        return -ESRCH;
863
}
864

865
int userdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
2,976✔
866
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
867
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
2,976✔
868
        int r;
2,976✔
869

870
        /* Well known errors this returns:
871
         *         -EINVAL    → user name is not valid
872
         *         -ESRCH     → no such user
873
         *         -ENOEXEC   → found a user by request UID or name, but it does not match filter
874
         *         -EHOSTDOWN → service failed for some reason
875
         *         -ETIMEDOUT → service timed out
876
         */
877

878
        assert(name);
2,976✔
879

880
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
2,976✔
881
                uid_t uid;
264✔
882

883
                if (parse_uid(name, &uid) >= 0)
264✔
884
                        return userdb_by_uid(uid, match, flags, ret);
191✔
885
        }
886

887
        if (!valid_user_group_name(name, VALID_USER_RELAX))
2,785✔
888
                return -EINVAL;
889

890
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
2,781✔
891
        if (r < 0)
2,781✔
892
                return r;
893

894
        r = query_append_uid_match(&query, match);
2,781✔
895
        if (r < 0)
2,781✔
896
                return r;
897

898
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
2,781✔
899
        if (!iterator)
2,781✔
900
                return -ENOMEM;
901

902
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
2,781✔
903
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
2,781✔
904
        if (r >= 0) {
2,781✔
905
                r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
1,451✔
906
                if (r == -ENOEXEC) /* found a user matching UID or name, but not filter. In this case the
1,451✔
907
                                    * fallback paths below are pointless */
908
                        return r;
909
        }
910
        if (r < 0) { /* If the above fails for any other reason, try fallback paths */
1,451✔
911
                r = userdb_by_name_fallbacks(name, iterator, flags, &ur);
1,804✔
912
                if (r < 0)
1,804✔
913
                        return r;
914
        }
915

916
        /* NB: we always apply our own filtering here, explicitly, regardless if the server supported it or
917
         * not. It's more robust this way, we never know how carefully the server is written, and whether it
918
         * properly implements all details of the filtering logic. */
919
        if (!user_record_match(ur, match))
2,185✔
920
                return -ENOEXEC;
921

922
        if (ret)
2,185✔
923
                *ret = TAKE_PTR(ur);
2,185✔
924

925
        return 0;
926
}
927

928
static int userdb_by_uid_fallbacks(
938✔
929
                uid_t uid,
930
                UserDBIterator *iterator,
931
                UserDBFlags flags,
932
                UserRecord **ret) {
933
        int r;
938✔
934

935
        assert(uid_is_valid(uid));
938✔
936
        assert(iterator);
938✔
937
        assert(ret);
938✔
938

939
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
938✔
940
                r = dropin_user_record_by_uid(uid, NULL, flags, ret);
717✔
941
                if (r >= 0)
717✔
942
                        return r;
943
        }
944

945
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
626✔
946
                r = userdb_iterator_block_nss_systemd(iterator);
226✔
947
                if (r >= 0) {
226✔
948
                        /* Client-side NSS fallback */
949
                        r = nss_user_record_by_uid(uid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
226✔
950
                        if (r >= 0)
226✔
951
                                return r;
952
                }
953
        }
954

955
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
490✔
956
                if (uid == 0)
309✔
957
                        return synthetic_root_user_build(ret);
1✔
958

959
                if (uid == UID_NOBODY && synthesize_nobody())
308✔
960
                        return synthetic_nobody_user_build(ret);
1✔
961
        }
962

963
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && uid_is_foreign(uid))
488✔
964
                return synthetic_foreign_user_build(uid - FOREIGN_UID_BASE, ret);
4✔
965

966
        return -ESRCH;
967
}
968

969
int userdb_by_uid(uid_t uid, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
1,897✔
970
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
971
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
1,897✔
972
        int r;
1,897✔
973

974
        if (!uid_is_valid(uid))
1,897✔
975
                return -EINVAL;
976

977
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(uid)));
1,897✔
978
        if (r < 0)
1,897✔
979
                return r;
980

981
        r = query_append_uid_match(&query, match);
1,897✔
982
        if (r < 0)
1,897✔
983
                return r;
984

985
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
1,897✔
986
        if (!iterator)
1,897✔
987
                return -ENOMEM;
988

989
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
1,897✔
990
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
1,897✔
991
        if (r >= 0) {
1,897✔
992
                r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
1,511✔
993
                if (r == -ENOEXEC)
1,511✔
994
                        return r;
995
        }
996
        if (r < 0) {
1,511✔
997
                r = userdb_by_uid_fallbacks(uid, iterator, flags, &ur);
938✔
998
                if (r < 0)
938✔
999
                        return r;
1000
        }
1001

1002
        if (!user_record_match(ur, match))
1,412✔
1003
                return -ENOEXEC;
1004

1005
        if (ret)
1,412✔
1006
                *ret = TAKE_PTR(ur);
1,412✔
1007

1008
        return 0;
1009
}
1010

1011
int userdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
41✔
1012
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1013
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
41✔
1014
        int r, qr;
41✔
1015

1016
        assert(ret);
41✔
1017

1018
        r = query_append_uid_match(&query, match);
41✔
1019
        if (r < 0)
41✔
1020
                return r;
1021

1022
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
41✔
1023
        if (!iterator)
41✔
1024
                return -ENOMEM;
1025

1026
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ true, query, flags);
41✔
1027

1028
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
41✔
1029
                r = userdb_iterator_block_nss_systemd(iterator);
17✔
1030
                if (r < 0)
17✔
1031
                        return r;
1032

1033
                setpwent();
17✔
1034
                iterator->nss_iterating = true;
17✔
1035
        }
1036

1037
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
41✔
1038
                r = conf_files_list_nulstr(
38✔
1039
                                &iterator->dropins,
19✔
1040
                                ".user",
1041
                                NULL,
1042
                                CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1043
                                USERDB_DROPIN_DIR_NULSTR("userdb"));
1044
                if (r < 0)
19✔
1045
                        log_debug_errno(r, "Failed to find user drop-ins, ignoring: %m");
×
1046
        }
1047

1048
        /* Note that we do not enumerate the foreign users, since those would be just 64K of noise */
1049

1050
        /* propagate IPC error, but only if there are no drop-ins */
1051
        if (qr < 0 &&
41✔
1052
            !iterator->nss_iterating &&
3✔
1053
            strv_isempty(iterator->dropins))
43✔
1054
                return qr;
1055

1056
        *ret = TAKE_PTR(iterator);
41✔
1057
        return 0;
41✔
1058
}
1059

1060
static int userdb_iterator_get_one(UserDBIterator *iterator, UserRecord **ret) {
812✔
1061
        int r;
812✔
1062

1063
        assert(iterator);
812✔
1064
        assert(iterator->what == LOOKUP_USER);
812✔
1065

1066
        if (iterator->nss_iterating) {
812✔
1067
                struct passwd *pw;
425✔
1068

1069
                /* If NSS isn't covered elsewhere, let's iterate through it first, since it probably contains
1070
                 * the more traditional sources, which are probably good to show first. */
1071

1072
                errno = 0;
425✔
1073
                pw = getpwent();
425✔
1074
                if (pw) {
425✔
1075
                        _cleanup_free_ char *buffer = NULL;
408✔
1076
                        bool incomplete = false;
408✔
1077
                        struct spwd spwd;
408✔
1078

1079
                        if (streq_ptr(pw->pw_name, "root"))
408✔
1080
                                iterator->synthesize_root = false;
17✔
1081
                        if (pw->pw_uid == UID_NOBODY)
408✔
1082
                                iterator->synthesize_nobody = false;
17✔
1083

1084
                        if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
408✔
1085
                                r = nss_spwd_for_passwd(pw, &spwd, &buffer);
408✔
1086
                                if (r < 0) {
408✔
1087
                                        log_debug_errno(r, "Failed to acquire shadow entry for user %s, ignoring: %m", pw->pw_name);
×
1088
                                        incomplete = ERRNO_IS_PRIVILEGE(r);
×
1089
                                }
1090
                        } else {
1091
                                r = -EUCLEAN;
1092
                                incomplete = true;
1093
                        }
1094

1095
                        r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
408✔
1096
                        if (r < 0)
408✔
1097
                                return r;
1098

1099
                        if (ret)
408✔
1100
                                (*ret)->incomplete = incomplete;
408✔
1101

1102
                        iterator->n_found++;
408✔
1103
                        return r;
408✔
1104
                }
1105

1106
                if (errno != 0)
17✔
1107
                        log_debug_errno(errno, "Failure to iterate NSS user database, ignoring: %m");
×
1108

1109
                iterator->nss_iterating = false;
17✔
1110
                endpwent();
17✔
1111
        }
1112

1113
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
441✔
1114
                const char *i = iterator->dropins[iterator->current_dropin];
74✔
1115
                _cleanup_free_ char *fn = NULL;
74✔
1116
                uid_t uid;
74✔
1117
                char *e;
74✔
1118

1119
                /* Next, let's add in the static drop-ins, which are quick to retrieve */
1120

1121
                r = path_extract_filename(i, &fn);
74✔
1122
                if (r < 0)
74✔
1123
                        return r;
1124

1125
                e = endswith(fn, ".user"); /* not actually a .user file? Then skip to next */
74✔
1126
                if (!e)
74✔
1127
                        continue;
×
1128

1129
                *e = 0; /* Chop off suffix */
74✔
1130

1131
                if (parse_uid(fn, &uid) < 0) /* not a UID .user file? Then skip to next */
74✔
1132
                        continue;
37✔
1133

1134
                r = dropin_user_record_by_uid(uid, i, iterator->flags, ret);
37✔
1135
                if (r < 0) {
37✔
1136
                        log_debug_errno(r, "Failed to parse user record for UID " UID_FMT ", ignoring: %m", uid);
×
1137
                        continue; /* If we failed to parse this record, let's suppress it from enumeration,
×
1138
                                   * and continue with the next record. Maybe someone is dropping it files
1139
                                   * and only partially wrote this one. */
1140
                }
1141

1142
                iterator->current_dropin++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
37✔
1143
                iterator->n_found++;
37✔
1144
                return 0;
37✔
1145
        }
1146

1147
        /* Then, let's return the users provided by varlink IPC */
1148
        r = userdb_process(iterator, ret, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
367✔
1149
        if (r < 0) {
367✔
1150

1151
                /* Finally, synthesize root + nobody if not done yet */
1152
                if (iterator->synthesize_root) {
50✔
1153
                        iterator->synthesize_root = false;
5✔
1154
                        iterator->n_found++;
5✔
1155
                        return synthetic_root_user_build(ret);
5✔
1156
                }
1157

1158
                if (iterator->synthesize_nobody) {
45✔
1159
                        iterator->synthesize_nobody = false;
5✔
1160
                        iterator->n_found++;
5✔
1161
                        return synthetic_nobody_user_build(ret);
5✔
1162
                }
1163

1164
                /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1165
                if (iterator->n_found > 0)
40✔
1166
                        return -ESRCH;
40✔
1167
        }
1168

1169
        return r;
1170
}
1171

1172
int userdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, UserRecord **ret) {
698✔
1173
        int r;
698✔
1174

1175
        assert(iterator);
698✔
1176
        assert(iterator->what == LOOKUP_USER);
698✔
1177

1178
        for (;;) {
114✔
1179
                _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
698✔
1180

1181
                r = userdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &ur : NULL);
812✔
1182
                if (r < 0)
812✔
1183
                        return r;
1184

1185
                if (ur && !user_record_match(ur, match))
772✔
1186
                        continue;
114✔
1187

1188
                if (ret)
658✔
1189
                        *ret = TAKE_PTR(ur);
657✔
1190

1191
                return r;
1192
        }
1193
}
1194

1195
static int synthetic_root_group_build(GroupRecord **ret) {
7✔
1196
        return group_record_build(
14✔
1197
                        ret,
1198
                        SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
7✔
1199
                                          SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(0)),
1200
                                          SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
1201
}
1202

1203
static int synthetic_nobody_group_build(GroupRecord **ret) {
6✔
1204
        return group_record_build(
12✔
1205
                        ret,
1206
                        SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME)),
6✔
1207
                                          SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(GID_NOBODY)),
1208
                                          SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
1209
}
1210

1211
static int synthetic_foreign_group_build(gid_t foreign_gid, GroupRecord **ret) {
2✔
1212
        assert(ret);
2✔
1213

1214
        if (!gid_is_valid(foreign_gid))
2✔
1215
                return -ESRCH;
2✔
1216
        if (foreign_gid > 0xFFFF)
2✔
1217
                return -ESRCH;
1218

1219
        _cleanup_free_ char *gn = NULL;
2✔
1220
        if (asprintf(&gn, "foreign-" GID_FMT, foreign_gid) < 0)
2✔
1221
                return -ENOMEM;
1222

1223
        _cleanup_free_ char *d = NULL;
2✔
1224
        if (asprintf(&d, "Foreign System Image GID " GID_FMT, foreign_gid) < 0)
2✔
1225
                return -ENOMEM;
1226

1227
        return group_record_build(
2✔
1228
                        ret,
1229
                        SD_JSON_BUILD_OBJECT(
2✔
1230
                                        SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(gn)),
1231
                                        SD_JSON_BUILD_PAIR("description", SD_JSON_BUILD_STRING(d)),
1232
                                        SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_gid)),
1233
                                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign"))));
1234
}
1235

1236
static int query_append_gid_match(sd_json_variant **query, const UserDBMatch *match) {
29,656✔
1237
        int r;
29,656✔
1238

1239
        assert(query);
29,656✔
1240

1241
        if (!userdb_match_is_set(match))
29,656✔
1242
                return 0;
1243

1244
        r = sd_json_variant_merge_objectbo(
14✔
1245
                        query,
1246
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
1247
                        SD_JSON_BUILD_PAIR_CONDITION(match->gid_min > 0, "gidMin", SD_JSON_BUILD_UNSIGNED(match->gid_min)),
1248
                        SD_JSON_BUILD_PAIR_CONDITION(match->gid_max < GID_INVALID-1, "gidMax", SD_JSON_BUILD_UNSIGNED(match->gid_max)));
1249
        if (r < 0)
12✔
1250
                return r;
1251

1252
        return query_append_disposition_mask(query, match->disposition_mask);
12✔
1253
}
1254

1255
static int groupdb_by_name_fallbacks(
25,822✔
1256
                const char *name,
1257
                UserDBIterator *iterator,
1258
                UserDBFlags flags,
1259
                GroupRecord **ret) {
1260

1261
        int r;
25,822✔
1262

1263
        assert(name);
25,822✔
1264
        assert(iterator);
25,822✔
1265
        assert(ret);
25,822✔
1266

1267
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
25,822✔
1268
                r = dropin_group_record_by_name(name, NULL, flags, ret);
11,494✔
1269
                if (r >= 0)
11,494✔
1270
                        return r;
1271
        }
1272

1273
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
25,796✔
1274
                r = userdb_iterator_block_nss_systemd(iterator);
100✔
1275
                if (r >= 0) {
100✔
1276
                        r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
100✔
1277
                        if (r >= 0)
100✔
1278
                                return r;
1279
                }
1280
        }
1281

1282
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
25,792✔
1283
                if (streq(name, "root"))
14,422✔
1284
                        return synthetic_root_group_build(ret);
1✔
1285

1286
                if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
14,421✔
1287
                        return synthetic_nobody_group_build(ret);
×
1288
        }
1289

1290
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
25,791✔
1291
                uid_t foreign_gid;
14,421✔
1292
                r = user_name_foreign_extract_uid(name, &foreign_gid); /* Same for UID + GID */
14,421✔
1293
                if (r < 0)
14,421✔
1294
                        return r;
×
1295
                if (r > 0)
14,421✔
1296
                        return synthetic_foreign_group_build(foreign_gid, ret);
×
1297
        }
1298

1299
        return -ESRCH;
1300
}
1301

1302
int groupdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
25,865✔
1303
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1304
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
25,865✔
1305
        int r;
25,865✔
1306

1307
        assert(name);
25,865✔
1308

1309
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
25,865✔
1310
                gid_t gid;
33✔
1311

1312
                if (parse_gid(name, &gid) >= 0)
33✔
1313
                        return groupdb_by_gid(gid, match, flags, ret);
9✔
1314
        }
1315

1316
        if (!valid_user_group_name(name, VALID_USER_RELAX))
25,856✔
1317
                return -EINVAL;
1318

1319
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
25,854✔
1320
        if (r < 0)
25,854✔
1321
                return r;
1322

1323
        r = query_append_gid_match(&query, match);
25,854✔
1324
        if (r < 0)
25,854✔
1325
                return r;
1326

1327
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
25,854✔
1328
        if (!iterator)
25,854✔
1329
                return -ENOMEM;
1330

1331
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
25,854✔
1332
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
25,854✔
1333
        if (r >= 0) {
25,854✔
1334
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
14,477✔
1335
                if (r == -ENOEXEC)
14,477✔
1336
                        return r;
1337
        }
1338
        if (r < 0) {
14,477✔
1339
                r = groupdb_by_name_fallbacks(name, iterator, flags, &gr);
25,822✔
1340
                if (r < 0)
25,822✔
1341
                        return r;
1342
        }
1343

1344
        /* As above, we apply our own client-side filtering even if server-side filtering worked, for robustness and simplicity reasons. */
1345
        if (!group_record_match(gr, match))
63✔
1346
                return -ENOEXEC;
1347

1348
        if (ret)
63✔
1349
                *ret = TAKE_PTR(gr);
63✔
1350

1351
        return 0;
1352
}
1353

1354
static int groupdb_by_gid_fallbacks(
3,477✔
1355
                gid_t gid,
1356
                UserDBIterator *iterator,
1357
                UserDBFlags flags,
1358
                GroupRecord **ret) {
1359
        int r;
3,477✔
1360

1361
        assert(gid_is_valid(gid));
3,477✔
1362
        assert(iterator);
3,477✔
1363
        assert(ret);
3,477✔
1364

1365
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
3,477✔
1366
                r = dropin_group_record_by_gid(gid, NULL, flags, ret);
1,184✔
1367
                if (r >= 0)
1,184✔
1368
                        return r;
1369
        }
1370

1371
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
3,443✔
1372
                r = userdb_iterator_block_nss_systemd(iterator);
141✔
1373
                if (r >= 0) {
141✔
1374
                        r = nss_group_record_by_gid(gid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
141✔
1375
                        if (r >= 0)
141✔
1376
                                return r;
1377
                }
1378
        }
1379

1380
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
3,402✔
1381
                if (gid == 0)
2,381✔
1382
                        return synthetic_root_group_build(ret);
1✔
1383

1384
                if (gid == GID_NOBODY && synthesize_nobody())
2,380✔
1385
                        return synthetic_nobody_group_build(ret);
1✔
1386
        }
1387

1388
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && gid_is_foreign(gid))
3,400✔
1389
                return synthetic_foreign_group_build(gid - FOREIGN_UID_BASE, ret);
2✔
1390

1391
        return -ESRCH;
1392
}
1393

1394
int groupdb_by_gid(gid_t gid, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
3,784✔
1395
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1396
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
3,784✔
1397
        int r;
3,784✔
1398

1399
        if (!gid_is_valid(gid))
3,784✔
1400
                return -EINVAL;
1401

1402
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(gid)));
3,784✔
1403
        if (r < 0)
3,784✔
1404
                return r;
1405

1406
        r = query_append_gid_match(&query, match);
3,784✔
1407
        if (r < 0)
3,784✔
1408
                return r;
1409

1410
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
3,784✔
1411
        if (!iterator)
3,784✔
1412
                return -ENOMEM;
1413

1414
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
3,784✔
1415
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
3,784✔
1416
        if (r >= 0) {
3,784✔
1417
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
2,728✔
1418
                if (r == -ENOEXEC)
2,728✔
1419
                        return r;
1420
        }
1421
        if (r < 0) {
2,728✔
1422
                r = groupdb_by_gid_fallbacks(gid, iterator, flags, &gr);
3,477✔
1423
                if (r < 0)
3,477✔
1424
                        return r;
1425
        }
1426

1427
        if (!group_record_match(gr, match))
386✔
1428
                return -ENOEXEC;
1429

1430
        if (ret)
386✔
1431
                *ret = TAKE_PTR(gr);
386✔
1432

1433
        return 0;
1434
}
1435

1436
int groupdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
18✔
1437
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1438
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
18✔
1439
        int r, qr;
18✔
1440

1441
        assert(ret);
18✔
1442

1443
        r = query_append_gid_match(&query, match);
18✔
1444
        if (r < 0)
18✔
1445
                return r;
1446

1447
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
18✔
1448
        if (!iterator)
18✔
1449
                return -ENOMEM;
1450

1451
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ true, query, flags);
18✔
1452

1453
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
18✔
1454
                r = userdb_iterator_block_nss_systemd(iterator);
8✔
1455
                if (r < 0)
8✔
1456
                        return r;
1457

1458
                setgrent();
8✔
1459
                iterator->nss_iterating = true;
8✔
1460
        }
1461

1462
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
18✔
1463
                r = conf_files_list_nulstr(
18✔
1464
                                &iterator->dropins,
9✔
1465
                                ".group",
1466
                                NULL,
1467
                                CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1468
                                USERDB_DROPIN_DIR_NULSTR("userdb"));
1469
                if (r < 0)
9✔
1470
                        log_debug_errno(r, "Failed to find group drop-ins, ignoring: %m");
×
1471
        }
1472

1473
        if (qr < 0 &&
18✔
1474
            !iterator->nss_iterating &&
1✔
1475
            strv_isempty(iterator->dropins))
19✔
1476
                return qr;
1477

1478
        *ret = TAKE_PTR(iterator);
18✔
1479
        return 0;
18✔
1480
}
1481

1482
static int groupdb_iterator_get_one(UserDBIterator *iterator, GroupRecord **ret) {
726✔
1483
        int r;
726✔
1484

1485
        assert(iterator);
726✔
1486
        assert(iterator->what == LOOKUP_GROUP);
726✔
1487

1488
        if (iterator->nss_iterating) {
726✔
1489
                struct group *gr;
456✔
1490

1491
                errno = 0;
456✔
1492
                gr = getgrent();
456✔
1493
                if (gr) {
456✔
1494
                        _cleanup_free_ char *buffer = NULL;
448✔
1495
                        bool incomplete = false;
448✔
1496
                        struct sgrp sgrp;
448✔
1497

1498
                        if (streq_ptr(gr->gr_name, "root"))
448✔
1499
                                iterator->synthesize_root = false;
8✔
1500
                        if (gr->gr_gid == GID_NOBODY)
448✔
1501
                                iterator->synthesize_nobody = false;
8✔
1502

1503
                        if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
448✔
1504
                                r = nss_sgrp_for_group(gr, &sgrp, &buffer);
448✔
1505
                                if (r < 0) {
448✔
1506
                                        log_debug_errno(r, "Failed to acquire shadow entry for group %s, ignoring: %m", gr->gr_name);
×
1507
                                        incomplete = ERRNO_IS_PRIVILEGE(r);
×
1508
                                }
1509
                        } else {
1510
                                r = -EUCLEAN;
1511
                                incomplete = true;
1512
                        }
1513

1514
                        r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
448✔
1515
                        if (r < 0)
448✔
1516
                                return r;
1517

1518
                        if (ret)
448✔
1519
                                (*ret)->incomplete = incomplete;
448✔
1520

1521
                        iterator->n_found++;
448✔
1522
                        return r;
448✔
1523
                }
1524

1525
                if (errno != 0)
8✔
1526
                        log_debug_errno(errno, "Failure to iterate NSS group database, ignoring: %m");
×
1527

1528
                iterator->nss_iterating = false;
8✔
1529
                endgrent();
8✔
1530
        }
1531

1532
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
296✔
1533
                const char *i = iterator->dropins[iterator->current_dropin];
36✔
1534
                _cleanup_free_ char *fn = NULL;
36✔
1535
                gid_t gid;
36✔
1536
                char *e;
36✔
1537

1538
                r = path_extract_filename(i, &fn);
36✔
1539
                if (r < 0)
36✔
1540
                        return r;
1541

1542
                e = endswith(fn, ".group");
36✔
1543
                if (!e)
36✔
1544
                        continue;
×
1545

1546
                *e = 0; /* Chop off suffix */
36✔
1547

1548
                if (parse_gid(fn, &gid) < 0)
36✔
1549
                        continue;
18✔
1550

1551
                r = dropin_group_record_by_gid(gid, i, iterator->flags, ret);
18✔
1552
                if (r < 0) {
18✔
1553
                        log_debug_errno(r, "Failed to parse group record for GID " GID_FMT ", ignoring: %m", gid);
×
1554
                        continue;
×
1555
                }
1556

1557
                iterator->current_dropin++;
18✔
1558
                iterator->n_found++;
18✔
1559
                return 0;
18✔
1560
        }
1561

1562
        r = userdb_process(iterator, NULL, ret, NULL, NULL);
260✔
1563
        if (r < 0) {
260✔
1564
                if (iterator->synthesize_root) {
28✔
1565
                        iterator->synthesize_root = false;
5✔
1566
                        iterator->n_found++;
5✔
1567
                        return synthetic_root_group_build(ret);
5✔
1568
                }
1569

1570
                if (iterator->synthesize_nobody) {
23✔
1571
                        iterator->synthesize_nobody = false;
5✔
1572
                        iterator->n_found++;
5✔
1573
                        return synthetic_nobody_group_build(ret);
5✔
1574
                }
1575

1576
                /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1577
                if (iterator->n_found > 0)
18✔
1578
                        return -ESRCH;
18✔
1579
        }
1580

1581
        return r;
1582
}
1583

1584
int groupdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, GroupRecord **ret) {
484✔
1585
        int r;
484✔
1586

1587
        assert(iterator);
484✔
1588
        assert(iterator->what == LOOKUP_GROUP);
484✔
1589

1590
        for (;;) {
242✔
1591
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
484✔
1592

1593
                r = groupdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &gr : NULL);
726✔
1594
                if (r < 0)
726✔
1595
                        return r;
1596

1597
                if (gr && !group_record_match(gr, match))
708✔
1598
                        continue;
242✔
1599

1600
                if (ret)
466✔
1601
                        *ret = TAKE_PTR(gr);
466✔
1602

1603
                return r;
1604
        }
1605
}
1606

1607
static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
13,706✔
1608
        int r;
13,706✔
1609

1610
        r = conf_files_list_nulstr(
13,706✔
1611
                        &i->dropins,
1612
                        ".membership",
1613
                        NULL,
1614
                        CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED,
1615
                        USERDB_DROPIN_DIR_NULSTR("userdb"));
1616
        if (r < 0)
13,706✔
1617
                log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
×
1618
}
13,706✔
1619

1620
int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1,589✔
1621
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1622
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
1,589✔
1623
        int r, qr;
1,589✔
1624

1625
        assert(ret);
1,589✔
1626

1627
        if (!valid_user_group_name(name, VALID_USER_RELAX))
1,589✔
1628
                return -EINVAL;
1629

1630
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
1,587✔
1631
        if (r < 0)
1,587✔
1632
                return r;
1633

1634
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1,587✔
1635
        if (!iterator)
1,587✔
1636
                return -ENOMEM;
1637

1638
        iterator->filter_user_name = strdup(name);
1,587✔
1639
        if (!iterator->filter_user_name)
1,587✔
1640
                return -ENOMEM;
1641

1642
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1,587✔
1643

1644
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1,587✔
1645
                r = userdb_iterator_block_nss_systemd(iterator);
124✔
1646
                if (r < 0)
124✔
1647
                        return r;
1648

1649
                setgrent();
124✔
1650
                iterator->nss_iterating = true;
124✔
1651
        }
1652

1653
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1,587✔
1654
                discover_membership_dropins(iterator, flags);
1,413✔
1655

1656
        if (qr < 0 &&
1,587✔
1657
            !iterator->nss_iterating &&
1,289✔
1658
            strv_isempty(iterator->dropins))
2,878✔
1659
                return qr;
1660

1661
        *ret = TAKE_PTR(iterator);
298✔
1662
        return 0;
298✔
1663
}
1664

1665
int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
28,697✔
1666
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1667
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
28,697✔
1668
        int r, qr;
28,697✔
1669

1670
        assert(ret);
28,697✔
1671

1672
        if (!valid_user_group_name(name, VALID_USER_RELAX))
28,697✔
1673
                return -EINVAL;
1674

1675
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
28,695✔
1676
        if (r < 0)
28,695✔
1677
                return r;
1678

1679
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
28,695✔
1680
        if (!iterator)
28,695✔
1681
                return -ENOMEM;
1682

1683
        iterator->filter_group_name = strdup(name);
28,695✔
1684
        if (!iterator->filter_group_name)
28,695✔
1685
                return -ENOMEM;
1686

1687
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
28,695✔
1688

1689
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
28,695✔
1690
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
20✔
1691

1692
                r = userdb_iterator_block_nss_systemd(iterator);
20✔
1693
                if (r < 0)
20✔
1694
                        return r;
1695

1696
                /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
1697
                (void) nss_group_record_by_name(name, false, &gr);
20✔
1698
                if (gr) {
20✔
1699
                        iterator->members_of_group = strv_copy(gr->members);
8✔
1700
                        if (!iterator->members_of_group)
8✔
1701
                                return -ENOMEM;
1702

1703
                        iterator->index_members_of_group = 0;
8✔
1704

1705
                        iterator->found_group_name = strdup(name);
8✔
1706
                        if (!iterator->found_group_name)
8✔
1707
                                return -ENOMEM;
1708
                }
1709
        }
1710

1711
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
28,695✔
1712
                discover_membership_dropins(iterator, flags);
12,291✔
1713

1714
        if (qr < 0 &&
28,695✔
1715
            strv_isempty(iterator->members_of_group) &&
12,271✔
1716
            strv_isempty(iterator->dropins))
40,968✔
1717
                return qr;
1718

1719
        *ret = TAKE_PTR(iterator);
16,424✔
1720
        return 0;
16,424✔
1721
}
1722

1723
int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
4✔
1724
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1725
        int r, qr;
4✔
1726

1727
        assert(ret);
4✔
1728

1729
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
4✔
1730
        if (!iterator)
4✔
1731
                return -ENOMEM;
1732

1733
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
4✔
1734

1735
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
4✔
1736
                r = userdb_iterator_block_nss_systemd(iterator);
2✔
1737
                if (r < 0)
2✔
1738
                        return r;
1739

1740
                setgrent();
2✔
1741
                iterator->nss_iterating = true;
2✔
1742
        }
1743

1744
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
4✔
1745
                discover_membership_dropins(iterator, flags);
2✔
1746

1747
        if (qr < 0 &&
4✔
1748
            !iterator->nss_iterating &&
×
1749
            strv_isempty(iterator->dropins))
4✔
1750
                return qr;
1751

1752
        *ret = TAKE_PTR(iterator);
4✔
1753
        return 0;
4✔
1754
}
1755

1756
int membershipdb_iterator_get(
16,792✔
1757
                UserDBIterator *iterator,
1758
                char **ret_user,
1759
                char **ret_group) {
1760

1761
        int r;
16,792✔
1762

1763
        assert(iterator);
16,792✔
1764

1765
        for (;;) {
16,856✔
1766
                /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1767
                if (!iterator->members_of_group) {
16,824✔
1768
                        struct group *g;
16,785✔
1769

1770
                        if (!iterator->nss_iterating)
16,785✔
1771
                                break;
1772

1773
                        assert(!iterator->found_user_name);
150✔
1774
                        do {
7,198✔
1775
                                errno = 0;
7,198✔
1776
                                g = getgrent();
7,198✔
1777
                                if (!g) {
7,198✔
1778
                                        if (errno != 0)
126✔
1779
                                                log_debug_errno(errno, "Failure during NSS group iteration, ignoring: %m");
×
1780
                                        break;
1781
                                }
1782

1783
                        } while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
7,184✔
1784
                                                              strv_isempty(g->gr_mem));
224✔
1785

1786
                        if (g) {
150✔
1787
                                r = free_and_strdup(&iterator->found_group_name, g->gr_name);
24✔
1788
                                if (r < 0)
24✔
1789
                                        return r;
1790

1791
                                if (iterator->filter_user_name)
24✔
1792
                                        iterator->members_of_group = strv_new(iterator->filter_user_name);
14✔
1793
                                else
1794
                                        iterator->members_of_group = strv_copy(g->gr_mem);
10✔
1795
                                if (!iterator->members_of_group)
24✔
1796
                                        return -ENOMEM;
1797

1798
                                iterator->index_members_of_group = 0;
24✔
1799
                        } else {
1800
                                iterator->nss_iterating = false;
126✔
1801
                                endgrent();
126✔
1802
                                break;
126✔
1803
                        }
1804
                }
1805

1806
                assert(iterator->found_group_name);
63✔
1807
                assert(iterator->members_of_group);
63✔
1808
                assert(!iterator->found_user_name);
63✔
1809

1810
                if (iterator->members_of_group[iterator->index_members_of_group]) {
63✔
1811
                        _cleanup_free_ char *cu = NULL, *cg = NULL;
31✔
1812

1813
                        if (ret_user) {
31✔
1814
                                cu = strdup(iterator->members_of_group[iterator->index_members_of_group]);
31✔
1815
                                if (!cu)
31✔
1816
                                        return -ENOMEM;
1817
                        }
1818

1819
                        if (ret_group) {
31✔
1820
                                cg = strdup(iterator->found_group_name);
31✔
1821
                                if (!cg)
31✔
1822
                                        return -ENOMEM;
1823
                        }
1824

1825
                        if (ret_user)
31✔
1826
                                *ret_user = TAKE_PTR(cu);
31✔
1827

1828
                        if (ret_group)
31✔
1829
                                *ret_group = TAKE_PTR(cg);
31✔
1830

1831
                        iterator->index_members_of_group++;
31✔
1832
                        return 0;
31✔
1833
                }
1834

1835
                iterator->members_of_group = strv_free(iterator->members_of_group);
32✔
1836
                iterator->found_group_name = mfree(iterator->found_group_name);
32✔
1837
        }
1838

1839
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
16,761✔
1840
                const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
×
1841
                _cleanup_free_ char *un = NULL, *gn = NULL;
×
1842

1843
                e = endswith(i, ".membership");
×
1844
                if (!e)
×
1845
                        continue;
×
1846

1847
                c = memchr(i, ':', e - i);
×
1848
                if (!c)
×
1849
                        continue;
×
1850

1851
                un = strndup(i, c - i);
×
1852
                if (!un)
×
1853
                        return -ENOMEM;
1854
                if (iterator->filter_user_name) {
×
1855
                        if (!streq(un, iterator->filter_user_name))
×
1856
                                continue;
×
1857
                } else if (!valid_user_group_name(un, VALID_USER_RELAX))
×
1858
                        continue;
×
1859

1860
                c++; /* skip over ':' */
×
1861
                gn = strndup(c, e - c);
×
1862
                if (!gn)
×
1863
                        return -ENOMEM;
1864
                if (iterator->filter_group_name) {
×
1865
                        if (!streq(gn, iterator->filter_group_name))
×
1866
                                continue;
×
1867
                } else if (!valid_user_group_name(gn, VALID_USER_RELAX))
×
1868
                        continue;
×
1869

1870
                iterator->current_dropin++;
×
1871
                iterator->n_found++;
×
1872

1873
                if (ret_user)
×
1874
                        *ret_user = TAKE_PTR(un);
×
1875
                if (ret_group)
×
1876
                        *ret_group = TAKE_PTR(gn);
×
1877

1878
                return 0;
1879
        }
1880

1881
        r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
16,761✔
1882
        if (r < 0 && iterator->n_found > 0)
16,761✔
1883
                return -ESRCH;
25✔
1884

1885
        return r;
1886
}
1887

1888
int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret) {
16,379✔
1889
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
16,379✔
1890
        _cleanup_strv_free_ char **members = NULL;
16,379✔
1891
        int r;
16,379✔
1892

1893
        assert(name);
16,379✔
1894
        assert(ret);
16,379✔
1895

1896
        r = membershipdb_by_group(name, flags, &iterator);
16,379✔
1897
        if (r < 0)
16,379✔
1898
                return r;
1899

1900
        for (;;) {
16,378✔
1901
                _cleanup_free_ char *user_name = NULL;
×
1902

1903
                r = membershipdb_iterator_get(iterator, &user_name, NULL);
16,378✔
1904
                if (r == -ESRCH)
16,378✔
1905
                        break;
1906
                if (r < 0)
×
1907
                        return r;
1908

1909
                r = strv_consume(&members, TAKE_PTR(user_name));
×
1910
                if (r < 0)
×
1911
                        return r;
1912
        }
1913

1914
        strv_sort_uniq(members);
16,378✔
1915

1916
        *ret = TAKE_PTR(members);
16,378✔
1917
        return 0;
16,378✔
1918
}
1919

1920
int userdb_block_nss_systemd(int b) {
2,198✔
1921
        _cleanup_(dlclosep) void *dl = NULL;
2,198✔
1922
        int (*call)(bool b);
2,198✔
1923

1924
        /* Note that we might be called from libnss_systemd.so.2 itself, but that should be fine, really. */
1925

1926
        dl = dlopen(LIBDIR "/libnss_systemd.so.2", RTLD_NOW|RTLD_NODELETE);
2,198✔
1927
        if (!dl) {
2,198✔
1928
                /* If the file isn't installed, don't complain loudly */
1929
                log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
×
1930
                return 0;
×
1931
        }
1932

1933
        log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2");
2,198✔
1934

1935
        call = dlsym(dl, "_nss_systemd_block");
2,198✔
1936
        if (!call)
2,198✔
1937
                /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1938
                return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
×
1939
                                       "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());
1940

1941
        return call(b);
2,198✔
1942
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc