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

systemd / systemd / 20561496515

28 Dec 2025 11:55AM UTC coverage: 72.478% (-0.2%) from 72.692%
20561496515

push

github

web-flow
core: several follow-ups for BindNetworkInterface= (#40202)

13 of 54 new or added lines in 5 files covered. (24.07%)

1051 existing lines in 43 files now uncovered.

309149 of 426542 relevant lines covered (72.48%)

1254687.42 hits per line

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

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

3
#include <gshadow.h>
4
#include <stdlib.h>
5

6
#include "sd-event.h"
7
#include "sd-varlink.h"
8

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

29
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, sd_varlink, sd_varlink_unref);
2,146✔
30

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

38
struct UserDBIterator {
39
        LookupWhat what;
40
        UserDBFlags flags;
41
        Set *links;
42

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

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

54
        char **dropins;
55
        size_t current_dropin;
56
        int error;
57
        unsigned n_found;
58

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

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

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

71
UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
79,539✔
72
        if (!iterator)
79,539✔
73
                return NULL;
74

75
        sd_json_variant_unref(iterator->query);
79,421✔
76

77
        set_free(iterator->links);
79,421✔
78
        strv_free(iterator->dropins);
79,421✔
79

80
        switch (iterator->what) {
79,421✔
81

82
        case LOOKUP_USER:
13,770✔
83
                user_record_unref(iterator->found_user);
13,770✔
84

85
                if (iterator->nss_iterating)
13,770✔
86
                        endpwent();
×
87

88
                break;
89

90
        case LOOKUP_GROUP:
37,319✔
91
                group_record_unref(iterator->found_group);
37,319✔
92

93
                if (iterator->nss_iterating)
37,319✔
94
                        endgrent();
×
95

96
                break;
97

98
        case LOOKUP_MEMBERSHIP:
28,332✔
99
                free(iterator->found_user_name);
28,332✔
100
                free(iterator->found_group_name);
28,332✔
101
                strv_free(iterator->members_of_group);
28,332✔
102
                free(iterator->filter_user_name);
28,332✔
103
                free(iterator->filter_group_name);
28,332✔
104

105
                if (iterator->nss_iterating)
28,332✔
106
                        endgrent();
×
107

108
                break;
109

110
        default:
×
111
                assert_not_reached();
×
112
        }
113

114
        sd_event_unref(iterator->event);
79,421✔
115

116
        if (iterator->nss_systemd_blocked)
79,421✔
117
                assert_se(userdb_block_nss_systemd(false) >= 0);
8,924✔
118

119
        return mfree(iterator);
79,421✔
120
}
121

122
static UserDBIterator* userdb_iterator_new(LookupWhat what, UserDBFlags flags) {
79,421✔
123
        UserDBIterator *i;
79,421✔
124

125
        assert(what >= 0);
79,421✔
126
        assert(what < _LOOKUP_WHAT_MAX);
79,421✔
127

128
        i = new(UserDBIterator, 1);
79,421✔
129
        if (!i)
79,421✔
130
                return NULL;
131

132
        *i = (UserDBIterator) {
79,421✔
133
                .what = what,
134
                .flags = flags,
135
                .synthesize_root = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC),
79,421✔
136
                .synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC),
137
        };
138

139
        return i;
79,421✔
140
}
141

142
static int userdb_iterator_block_nss_systemd(UserDBIterator *iterator) {
8,924✔
143
        int r;
8,924✔
144

145
        assert(iterator);
8,924✔
146

147
        if (iterator->nss_systemd_blocked)
8,924✔
148
                return 0;
149

150
        r = userdb_block_nss_systemd(true);
8,924✔
151
        if (r < 0)
8,924✔
152
                return r;
153

154
        iterator->nss_systemd_blocked = true;
8,924✔
155
        return 1;
8,924✔
156
}
157

158
struct user_group_data {
159
        sd_json_variant *record;
160
        bool incomplete;
161
};
162

163
static void user_group_data_done(struct user_group_data *d) {
6,167✔
164
        sd_json_variant_unref(d->record);
6,167✔
165
}
6,167✔
166

167
struct membership_data {
168
        char *user_name;
169
        char *group_name;
170
};
171

172
static void membership_data_done(struct membership_data *d) {
236✔
173
        free(d->user_name);
236✔
174
        free(d->group_name);
236✔
175
}
236✔
176

177
static int userdb_maybe_restart_query(
151,167✔
178
                UserDBIterator *iterator,
179
                sd_varlink *link,
180
                sd_json_variant *parameters,
181
                const char *error_id) {
182

183
        int r;
151,167✔
184

185
        assert(iterator);
151,167✔
186
        assert(link);
151,167✔
187
        assert(error_id);
151,167✔
188

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

202
        /* Figure out if the reported error indicates any of the suppressible fields are at fault, and that
203
         * our query actually included them */
204
        bool restart = false;
1,016,375✔
205
        STRV_FOREACH(f, fields) {
1,016,375✔
206
                if (!sd_varlink_error_is_invalid_parameter(error_id, parameters, *f))
888,353✔
207
                        continue;
865,208✔
208

209
                if (!sd_json_variant_by_key(iterator->query, *f))
23,145✔
210
                        continue;
×
211

212
                restart = true;
213
                break;
214
        }
215

216
        if (!restart)
151,167✔
217
                return 0;
151,167✔
218

219
        /* Now patch the fields out */
220
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query =
151,167✔
221
                sd_json_variant_ref(iterator->query);
23,145✔
222

223
        r = sd_json_variant_filter(&patched_query, (char**const) fields);
23,145✔
224
        if (r < 0)
23,145✔
225
                return r;
226

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

237
        log_debug("Restarted query to service '%s' due to missing features.", sd_varlink_get_description(link));
23,145✔
238
        return 1;
239
}
240

241
static int userdb_on_query_reply(
157,570✔
242
                sd_varlink *link,
243
                sd_json_variant *parameters,
244
                const char *error_id,
245
                sd_varlink_reply_flags_t flags,
246
                void *userdata) {
247

248
        UserDBIterator *iterator = ASSERT_PTR(userdata);
157,570✔
249
        int r;
157,570✔
250

251
        if (error_id) {
157,570✔
252
                log_debug("Got lookup error: %s", error_id);
151,167✔
253

254
                r = userdb_maybe_restart_query(iterator, link, parameters, error_id);
151,167✔
255
                if (r < 0)
151,167✔
256
                        return r;
257
                if (r > 0) {
151,167✔
258
                        r = 0;
23,145✔
259
                        goto finish;
23,145✔
260
                }
261

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

283
                goto finish;
128,022✔
284
        }
285

286
        switch (iterator->what) {
6,403✔
287

288
        case LOOKUP_USER: {
3,341✔
289
                _cleanup_(user_group_data_done) struct user_group_data user_data = {};
3,341✔
290

291
                static const sd_json_dispatch_field dispatch_table[] = {
3,341✔
292
                        { "record",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
293
                        { "incomplete", SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool, offsetof(struct user_group_data, incomplete), 0 },
294
                        {}
295
                };
296
                _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
2,925✔
297

298
                assert_se(!iterator->found_user);
3,341✔
299

300
                r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &user_data);
3,341✔
301
                if (r < 0)
3,341✔
302
                        goto finish;
×
303

304
                if (!user_data.record) {
3,341✔
305
                        r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
×
306
                        goto finish;
×
307
                }
308

309
                hr = user_record_new();
3,341✔
310
                if (!hr) {
3,341✔
311
                        r = -ENOMEM;
×
312
                        goto finish;
×
313
                }
314

315
                r = user_record_load(hr, user_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
3,341✔
316
                if (r < 0)
3,341✔
317
                        goto finish;
×
318

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

324
                hr->incomplete = user_data.incomplete;
3,341✔
325

326
                /* We match the root user by the name since the name is our primary key. We match the nobody
327
                 * use by UID though, since the name might differ on OSes */
328
                if (streq_ptr(hr->user_name, "root"))
3,341✔
329
                        iterator->synthesize_root = false;
542✔
330
                if (hr->uid == UID_NOBODY)
3,341✔
331
                        iterator->synthesize_nobody = false;
19✔
332

333
                iterator->found_user = TAKE_PTR(hr);
3,341✔
334
                iterator->n_found++;
3,341✔
335

336
                /* More stuff coming? then let's just exit cleanly here */
337
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
3,341✔
338
                        return 0;
416✔
339

340
                /* Otherwise, let's remove this link and exit cleanly then */
341
                r = 0;
2,925✔
342
                goto finish;
2,925✔
343
        }
344

345
        case LOOKUP_GROUP: {
2,826✔
346
                _cleanup_(user_group_data_done) struct user_group_data group_data = {};
2,826✔
347

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

355
                assert_se(!iterator->found_group);
2,826✔
356

357
                r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &group_data);
2,826✔
358
                if (r < 0)
2,826✔
359
                        goto finish;
×
360

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

366
                g = group_record_new();
2,826✔
367
                if (!g) {
2,826✔
368
                        r = -ENOMEM;
×
369
                        goto finish;
×
370
                }
371

372
                r = group_record_load(g, group_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
2,826✔
373
                if (r < 0)
2,826✔
374
                        goto finish;
×
375

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

381
                g->incomplete = group_data.incomplete;
2,826✔
382

383
                if (streq_ptr(g->group_name, "root"))
2,826✔
384
                        iterator->synthesize_root = false;
27✔
385
                if (g->gid == GID_NOBODY)
2,826✔
386
                        iterator->synthesize_nobody = false;
14✔
387

388
                iterator->found_group = TAKE_PTR(g);
2,826✔
389
                iterator->n_found++;
2,826✔
390

391
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
2,826✔
392
                        return 0;
413✔
393

394
                r = 0;
2,413✔
395
                goto finish;
2,413✔
396
        }
397

398
        case LOOKUP_MEMBERSHIP: {
236✔
399
                _cleanup_(membership_data_done) struct membership_data membership_data = {};
226✔
400

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

407
                assert(!iterator->found_user_name);
236✔
408
                assert(!iterator->found_group_name);
236✔
409

410
                r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &membership_data);
236✔
411
                if (r < 0)
236✔
412
                        goto finish;
×
413

414
                iterator->found_user_name = TAKE_PTR(membership_data.user_name);
236✔
415
                iterator->found_group_name = TAKE_PTR(membership_data.group_name);
236✔
416
                iterator->n_found++;
236✔
417

418
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
236✔
419
                        return 0;
10✔
420

421
                r = 0;
226✔
422
                goto finish;
226✔
423
        }
424

425
        default:
×
426
                assert_not_reached();
×
427
        }
428

429
finish:
5,564✔
430
        /* If we got one ENOEXEC, let that win. Similarly, ESRCH wins except for ENOEXEC. This way when we do
431
         * a wild dump we won't be tripped up by bad errors – as long as at least one connection ended
432
         * somewhat cleanly. */
433
        if (r == -ENOEXEC ||
156,731✔
434
            (r == -ESRCH && iterator->error != ENOEXEC) ||
128,007✔
435
            iterator->error == 0)
28,724✔
436
                iterator->error = -r;
152,083✔
437

438
        assert_se(set_remove(iterator->links, link) == link);
156,731✔
439
        link = sd_varlink_unref(link);
156,731✔
440
        return 0;
156,731✔
441
}
442

443
static int userdb_connect(
158,885✔
444
                UserDBIterator *iterator,
445
                const char *path,
446
                const char *method,
447
                bool more,
448
                sd_json_variant *query) {
449

450
        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
158,885✔
451
        int r;
158,885✔
452

453
        assert(iterator);
158,885✔
454
        assert(path);
158,885✔
455
        assert(method);
158,885✔
456

457
        r = sd_varlink_connect_address(&vl, path);
158,885✔
458
        if (r < 0)
158,885✔
459
                return log_debug_errno(r, "Unable to connect to %s: %m", path);
8✔
460

461
        sd_varlink_set_userdata(vl, iterator);
158,877✔
462

463
        if (!iterator->event) {
158,877✔
464
                r = sd_event_new(&iterator->event);
50,482✔
465
                if (r < 0)
50,482✔
466
                        return log_debug_errno(r, "Unable to allocate event loop: %m");
×
467
        }
468

469
        r = sd_varlink_attach_event(vl, iterator->event, SD_EVENT_PRIORITY_NORMAL);
158,877✔
470
        if (r < 0)
158,877✔
471
                return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m");
×
472

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

480
        r = sd_varlink_bind_reply(vl, userdb_on_query_reply);
158,877✔
481
        if (r < 0)
158,877✔
482
                return log_debug_errno(r, "Failed to bind reply callback: %m");
×
483

484
        _cleanup_free_ char *service = NULL;
158,877✔
485
        r = path_extract_filename(path, &service);
158,877✔
486
        if (r < 0)
158,877✔
487
                return log_debug_errno(r, "Failed to extract service name from socket path: %m");
×
488
        assert(r != O_DIRECTORY);
158,877✔
489

490
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query = sd_json_variant_ref(query);
317,754✔
491
        r = sd_json_variant_set_field_string(&patched_query, "service", service);
158,877✔
492
        if (r < 0)
158,877✔
493
                return log_debug_errno(r, "Unable to set service JSON field: %m");
×
494

495
        if (more)
158,877✔
496
                r = sd_varlink_observe(vl, method, patched_query);
45,957✔
497
        else
498
                r = sd_varlink_invoke(vl, method, patched_query);
112,920✔
499
        if (r < 0)
158,877✔
500
                return log_debug_errno(r, "Failed to invoke varlink method: %m");
×
501

502
        r = set_ensure_consume(&iterator->links, &link_hash_ops, TAKE_PTR(vl));
158,877✔
503
        if (r < 0)
158,877✔
504
                return log_debug_errno(r, "Failed to add varlink connection to set: %m");
×
505
        return r;
506
}
507

508
static int userdb_start_query(
79,421✔
509
                UserDBIterator *iterator,
510
                const char *method, /* must be a static string, we are not going to copy this here! */
511
                bool more,
512
                sd_json_variant *query,
513
                UserDBFlags flags) {
514

515
        _cleanup_strv_free_ char **except = NULL, **only = NULL;
79,421✔
516
        _cleanup_closedir_ DIR *d = NULL;
79,421✔
517
        const char *e;
79,421✔
518
        int r, ret = 0;
79,421✔
519

520
        assert(iterator);
79,421✔
521
        assert(method);
79,421✔
522

523
        if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
79,421✔
524
                return -ENOLINK;
525

526
        assert(!iterator->query);
50,490✔
527
        iterator->method = method; /* note: we don't make a copy here! */
50,490✔
528
        iterator->query = sd_json_variant_ref(query);
50,490✔
529
        iterator->more = more;
50,490✔
530

531
        e = getenv("SYSTEMD_BYPASS_USERDB");
50,490✔
532
        if (e) {
50,490✔
533
                r = parse_boolean(e);
9,595✔
534
                if (r > 0)
9,595✔
535
                        return -ENOLINK;
536
                if (r < 0) {
9,595✔
537
                        except = strv_split(e, ":");
9,595✔
538
                        if (!except)
9,595✔
539
                                return -ENOMEM;
540
                }
541
        }
542

543
        e = getenv("SYSTEMD_ONLY_USERDB");
50,490✔
544
        if (e) {
50,490✔
545
                only = strv_split(e, ":");
×
546
                if (!only)
×
547
                        return -ENOMEM;
548
        }
549

550
        /* First, let's talk to the multiplexer, if we can */
551
        if ((flags & (USERDB_AVOID_MULTIPLEXER|USERDB_EXCLUDE_DYNAMIC_USER|USERDB_EXCLUDE_NSS|USERDB_EXCLUDE_DROPIN|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN)) == 0 &&
60,438✔
552
            !strv_contains(except, "io.systemd.Multiplexer") &&
9,948✔
553
            (!only || strv_contains(only, "io.systemd.Multiplexer"))) {
9,948✔
554
                r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, query);
9,948✔
555
                if (r >= 0) {
9,948✔
556
                        iterator->nss_covered = true; /* The multiplexer does NSS */
9,948✔
557
                        iterator->dropin_covered = true; /* It also handles drop-in stuff */
9,948✔
558
                        return 0;
9,948✔
559
                }
560
        }
561

562
        d = opendir("/run/systemd/userdb/");
40,542✔
563
        if (!d) {
40,542✔
564
                if (errno == ENOENT)
×
565
                        return -ESRCH;
566

567
                return -errno;
×
568
        }
569

570
        FOREACH_DIRENT(de, d, return -errno) {
338,069✔
571
                _cleanup_free_ char *p = NULL;
125,792✔
572
                bool is_nss, is_dropin;
216,443✔
573

574
                if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
216,443✔
575
                        continue;
40,517✔
576

577
                if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
175,926✔
578
                    streq(de->d_name, "io.systemd.DynamicUser"))
30✔
579
                        continue;
6✔
580

581
                /* Avoid NSS if this is requested. Note that we also skip NSS when we were asked to skip the
582
                 * multiplexer, since in that case it's safer to do NSS in the client side emulation below
583
                 * (and when we run as part of systemd-userdbd.service we don't want to talk to ourselves
584
                 * anyway). */
585
                is_nss = streq(de->d_name, "io.systemd.NameServiceSwitch");
175,920✔
586
                if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
175,920✔
587
                        continue;
40,445✔
588

589
                /* Similar for the drop-in service */
590
                is_dropin = streq(de->d_name, "io.systemd.DropIn");
135,475✔
591
                if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
135,475✔
592
                        continue;
9,511✔
593

594
                if (strv_contains(except, de->d_name))
125,964✔
595
                        continue;
172✔
596

597
                if (only && !strv_contains(only, de->d_name))
125,792✔
598
                        continue;
×
599

600
                p = path_join("/run/systemd/userdb/", de->d_name);
125,792✔
601
                if (!p)
125,792✔
602
                        return -ENOMEM;
×
603

604
                r = userdb_connect(iterator, p, method, more, query);
125,792✔
605
                if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
125,792✔
606
                                       * and could connect to it */
607
                        iterator->nss_covered = true;
72✔
608
                if (is_dropin && r >= 0)
125,792✔
609
                        iterator->dropin_covered = true;
31,006✔
610

611
                RET_GATHER(ret, r);
125,792✔
612
        }
613

614
        if (set_isempty(iterator->links))
40,542✔
615
                return ret < 0 ? ret : -ESRCH; /* propagate the first error we saw if we couldn't connect to anything. */
8✔
616

617
        /* We connected to some services, in this case, ignore the ones we failed on */
618
        return 0;
619
}
620

621
static int userdb_process(
51,590✔
622
                UserDBIterator *iterator,
623
                UserRecord **ret_user_record,
624
                GroupRecord **ret_group_record,
625
                char **ret_user_name,
626
                char **ret_group_name) {
627

628
        int r;
51,590✔
629

630
        assert(iterator);
51,590✔
631

632
        for (;;) {
808,768✔
633
                if (iterator->what == LOOKUP_USER && iterator->found_user) {
808,768✔
634
                        if (ret_user_record)
3,341✔
635
                                *ret_user_record = TAKE_PTR(iterator->found_user);
3,341✔
636
                        else
637
                                iterator->found_user = user_record_unref(iterator->found_user);
×
638

639
                        if (ret_group_record)
3,341✔
640
                                *ret_group_record = NULL;
×
641
                        if (ret_user_name)
3,341✔
642
                                *ret_user_name = NULL;
×
643
                        if (ret_group_name)
3,341✔
644
                                *ret_group_name = NULL;
×
645

646
                        return 0;
3,341✔
647
                }
648

649
                if (iterator->what == LOOKUP_GROUP && iterator->found_group) {
805,427✔
650
                        if (ret_group_record)
2,826✔
651
                                *ret_group_record = TAKE_PTR(iterator->found_group);
2,826✔
652
                        else
653
                                iterator->found_group = group_record_unref(iterator->found_group);
×
654

655
                        if (ret_user_record)
2,826✔
656
                                *ret_user_record = NULL;
×
657
                        if (ret_user_name)
2,826✔
658
                                *ret_user_name = NULL;
×
659
                        if (ret_group_name)
2,826✔
660
                                *ret_group_name = NULL;
×
661

662
                        return 0;
2,826✔
663
                }
664

665
                if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
802,601✔
666
                        if (ret_user_name)
236✔
667
                                *ret_user_name = TAKE_PTR(iterator->found_user_name);
130✔
668
                        else
669
                                iterator->found_user_name = mfree(iterator->found_user_name);
106✔
670

671
                        if (ret_group_name)
236✔
672
                                *ret_group_name = TAKE_PTR(iterator->found_group_name);
210✔
673
                        else
674
                                iterator->found_group_name = mfree(iterator->found_group_name);
26✔
675

676
                        if (ret_user_record)
236✔
677
                                *ret_user_record = NULL;
×
678
                        if (ret_group_record)
236✔
679
                                *ret_group_record = NULL;
×
680

681
                        return 0;
236✔
682
                }
683

684
                if (set_isempty(iterator->links)) {
802,365✔
685
                        if (iterator->error == 0)
45,187✔
686
                                return -ESRCH;
687

688
                        return -abs(iterator->error);
45,045✔
689
                }
690

691
                if (!iterator->event)
757,178✔
692
                        return -ESRCH;
693

694
                r = sd_event_run(iterator->event, UINT64_MAX);
757,178✔
695
                if (r < 0)
757,178✔
696
                        return r;
697
        }
698
}
699

700
static int synthetic_root_user_build(UserRecord **ret) {
5✔
701
        return user_record_buildo(
5✔
702
                        ret,
703
                        SD_JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING("root")),
704
                        SD_JSON_BUILD_PAIR_UNSIGNED("uid", 0),
705
                        SD_JSON_BUILD_PAIR_UNSIGNED("gid", 0),
706
                        SD_JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/root")),
707
                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
708
}
709

710
static int synthetic_nobody_user_build(UserRecord **ret) {
4✔
711
        return user_record_buildo(
4✔
712
                        ret,
713
                        SD_JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING(NOBODY_USER_NAME)),
714
                        SD_JSON_BUILD_PAIR_UNSIGNED("uid", UID_NOBODY),
715
                        SD_JSON_BUILD_PAIR_UNSIGNED("gid", GID_NOBODY),
716
                        SD_JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
717
                        SD_JSON_BUILD_PAIR_BOOLEAN("locked", true),
718
                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
719
}
720

721
static int synthetic_foreign_user_build(uid_t foreign_uid, UserRecord **ret) {
14✔
722
        assert(ret);
14✔
723

724
        if (!uid_is_valid(foreign_uid))
14✔
725
                return -ESRCH;
14✔
726
        if (foreign_uid > 0xFFFF)
13✔
727
                return -ESRCH;
728

729
        _cleanup_free_ char *un = NULL;
13✔
730
        if (asprintf(&un, "foreign-" UID_FMT, foreign_uid) < 0)
13✔
731
                return -ENOMEM;
732

733
        _cleanup_free_ char *rn = NULL;
13✔
734
        if (asprintf(&rn, "Foreign System Image UID " UID_FMT, foreign_uid) < 0)
13✔
735
                return -ENOMEM;
736

737
        return user_record_buildo(
13✔
738
                        ret,
739
                        SD_JSON_BUILD_PAIR_STRING("userName", un),
740
                        SD_JSON_BUILD_PAIR_STRING("realName", rn),
741
                        SD_JSON_BUILD_PAIR_UNSIGNED("uid", FOREIGN_UID_BASE + foreign_uid),
742
                        SD_JSON_BUILD_PAIR_UNSIGNED("gid", FOREIGN_UID_BASE + foreign_uid),
743
                        SD_JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
744
                        SD_JSON_BUILD_PAIR_BOOLEAN("locked", true),
745
                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign")));
746
}
747

748
static int synthetic_numeric_user_build(uid_t uid, UserRecord **ret) {
2,931✔
749
        assert(ret);
2,931✔
750

751
        if (uid == 0) /* This should be handled by synthetic_root_user_build() */
2,931✔
752
                return -ESRCH;
2,931✔
753

754
        if (!uid_is_system(uid))
2,931✔
755
                return -ESRCH;
756

757
        _cleanup_free_ char *un = NULL;
2,928✔
758
        if (asprintf(&un, "unknown-" UID_FMT, uid) < 0)
2,928✔
759
                return -ENOMEM;
760

761
        _cleanup_free_ char *rn = NULL;
2,928✔
762
        if (asprintf(&rn, "Unknown System UID " UID_FMT, uid) < 0)
2,928✔
763
                return -ENOMEM;
764

765
        return user_record_buildo(
2,928✔
766
                        ret,
767
                        SD_JSON_BUILD_PAIR_STRING("userName", un),
768
                        SD_JSON_BUILD_PAIR_STRING("realName", rn),
769
                        SD_JSON_BUILD_PAIR_UNSIGNED("uid", uid),
770
                        SD_JSON_BUILD_PAIR_STRING("disposition", "system"));
771
}
772

773
static int user_name_foreign_extract_uid(const char *name, uid_t *ret_uid) {
12,835✔
774
        int r;
12,835✔
775

776
        assert(name);
12,835✔
777
        assert(ret_uid);
12,835✔
778

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

782
        const char *e = startswith(name, "foreign-");
12,835✔
783
        if (!e)
12,835✔
784
                goto nomatch;
12,826✔
785

786
        uid_t uid;
9✔
787
        r = parse_uid(e, &uid);
9✔
788
        if (r < 0)
9✔
789
                goto nomatch;
1✔
790

791
        if (uid > 0xFFFF)
8✔
792
                goto nomatch;
1✔
793

794
        *ret_uid = uid;
7✔
795
        return 1;
7✔
796

797
nomatch:
12,828✔
798
        *ret_uid = UID_INVALID;
12,828✔
799
        return 0;
12,828✔
800
}
801

802
static int query_append_common(sd_json_variant **query, const UserDBMatch *match) {
15,881✔
803
        int r;
15,881✔
804
        _cleanup_strv_free_ char **dispositions = NULL;
15,881✔
805

806
        assert(query);
15,881✔
807

808
        uint64_t mask = match->disposition_mask;
15,881✔
809
        if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL)) {
15,881✔
810
                for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
64,200✔
811
                        if (!BIT_SET(mask, d))
56,175✔
812
                                continue;
×
813

814
                        r = strv_extend(&dispositions, user_disposition_to_string(d));
56,175✔
815
                        if (r < 0)
56,175✔
816
                                return r;
817
                }
818
        }
819

820
        return sd_json_variant_merge_objectbo(
39,790✔
821
                        query,
822
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
823
                        SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(match->uuid), "uuid", SD_JSON_BUILD_UUID(match->uuid)),
824
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(dispositions), "dispositionMask", SD_JSON_BUILD_STRV(dispositions)));
825
}
826

827
static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *match) {
13,770✔
828
        int r;
13,770✔
829

830
        assert(query);
13,770✔
831

832
        if (!userdb_match_is_set(match))
13,770✔
833
                return 0;
834

835
        r = sd_json_variant_merge_objectbo(
6,249✔
836
                        query,
837
                        SD_JSON_BUILD_PAIR_CONDITION(match->uid_min > 0, "uidMin", SD_JSON_BUILD_UNSIGNED(match->uid_min)),
838
                        SD_JSON_BUILD_PAIR_CONDITION(match->uid_max < UID_INVALID-1, "uidMax", SD_JSON_BUILD_UNSIGNED(match->uid_max)));
839
        if (r < 0)
6,249✔
840
                return r;
841

842
        return query_append_common(query, match);
6,249✔
843
}
844

845
static int userdb_by_name_fallbacks(
3,390✔
846
                const char *name,
847
                UserDBIterator *iterator,
848
                UserDBFlags flags,
849
                UserRecord **ret) {
850
        int r;
3,390✔
851

852
        assert(name);
3,390✔
853
        assert(iterator);
3,390✔
854
        assert(ret);
3,390✔
855

856
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
3,390✔
857
                r = dropin_user_record_by_name(name, /* path= */ NULL, flags, ret);
3,182✔
858
                if (r >= 0)
3,182✔
859
                        return r;
860
        }
861

862
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
1,107✔
863
                /* Make sure the NSS lookup doesn't recurse back to us. */
864

865
                r = userdb_iterator_block_nss_systemd(iterator);
380✔
866
                if (r >= 0) {
380✔
867
                        /* Client-side NSS fallback */
868
                        r = nss_user_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
380✔
869
                        if (r >= 0)
380✔
870
                                return r;
871
                }
872
        }
873

874
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
846✔
875
                if (streq(name, "root"))
311✔
876
                        return synthetic_root_user_build(ret);
1✔
877

878
                if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
310✔
879
                        return synthetic_nobody_user_build(ret);
×
880
        }
881

882
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
845✔
883
                uid_t foreign_uid;
310✔
884
                r = user_name_foreign_extract_uid(name, &foreign_uid);
310✔
885
                if (r < 0)
310✔
886
                        return r;
3✔
887
                if (r > 0)
310✔
888
                        return synthetic_foreign_user_build(foreign_uid, ret);
3✔
889
        }
890

891
        return -ESRCH;
892
}
893

894
int userdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
8,096✔
895
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
896
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
8,096✔
897
        int r;
8,096✔
898

899
        /* Well known errors this returns:
900
         *         -EINVAL    → user name is not valid
901
         *         -ESRCH     → no such user
902
         *         -ENOEXEC   → found a user by request UID or name, but it does not match filter
903
         *         -EHOSTDOWN → service failed for some reason
904
         *         -ETIMEDOUT → service timed out
905
         */
906

907
        assert(name);
8,096✔
908

909
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
8,096✔
910
                uid_t uid;
3,405✔
911

912
                if (parse_uid(name, &uid) >= 0)
3,405✔
913
                        return userdb_by_uid(uid, match, flags, ret);
3,212✔
914
        }
915

916
        if (!valid_user_group_name(name, VALID_USER_RELAX))
4,884✔
917
                return -EINVAL;
918

919
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_STRING("userName", name));
4,877✔
920
        if (r < 0)
4,877✔
921
                return r;
922

923
        r = query_append_uid_match(&query, match);
4,877✔
924
        if (r < 0)
4,877✔
925
                return r;
926

927
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
4,877✔
928
        if (!iterator)
4,877✔
929
                return -ENOMEM;
930

931
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
4,877✔
932
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
4,877✔
933
        if (r >= 0) {
4,877✔
934
                r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
2,223✔
935
                if (r == -ENOEXEC) /* Found a user matching UID or name, but not filter. In this case the
2,223✔
936
                                    * fallback paths below are pointless */
937
                        return r;
938
        }
939
        if (r < 0) { /* If the above fails for any other reason, try fallback paths */
2,223✔
940
                r = userdb_by_name_fallbacks(name, iterator, flags, &ur);
3,390✔
941
                if (r < 0)
3,390✔
942
                        return r;
943
        }
944

945
        /* NB: we always apply our own filtering here, explicitly, regardless if the server supported it or
946
         * not. It's more robust this way, we never know how carefully the server is written, and whether it
947
         * properly implements all details of the filtering logic. */
948
        if (!user_record_match(ur, match))
4,035✔
949
                return -ENOEXEC;
950

951
        if (ret)
4,032✔
952
                *ret = TAKE_PTR(ur);
4,032✔
953

954
        return 0;
955
}
956

957
static int userdb_by_uid_fallbacks(
7,443✔
958
                uid_t uid,
959
                UserDBIterator *iterator,
960
                UserDBFlags flags,
961
                UserRecord **ret) {
962
        int r;
7,443✔
963

964
        assert(uid_is_valid(uid));
7,443✔
965
        assert(iterator);
7,443✔
966
        assert(ret);
7,443✔
967

968
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
7,443✔
969
                r = dropin_user_record_by_uid(uid, NULL, flags, ret);
4,240✔
970
                if (r >= 0)
4,240✔
971
                        return r;
972
        }
973

974
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
6,761✔
975
                r = userdb_iterator_block_nss_systemd(iterator);
3,266✔
976
                if (r >= 0) {
3,266✔
977
                        /* Client-side NSS fallback */
978
                        r = nss_user_record_by_uid(uid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
3,266✔
979
                        if (r >= 0)
3,266✔
980
                                return r;
981
                }
982
        }
983

984
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
6,539✔
985
                if (uid == 0)
6,247✔
986
                        return synthetic_root_user_build(ret);
1✔
987

988
                if (uid == UID_NOBODY && synthesize_nobody())
6,246✔
989
                        return synthetic_nobody_user_build(ret);
1✔
990
        }
991

992
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && uid_is_foreign(uid))
6,537✔
993
                return synthetic_foreign_user_build(uid - FOREIGN_UID_BASE, ret);
11✔
994

995
        if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
6,526✔
996
                return synthetic_numeric_user_build(uid, ret);
2,931✔
997

998
        return -ESRCH;
999
}
1000

1001
int userdb_by_uid(uid_t uid, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
8,860✔
1002
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1003
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
8,860✔
1004
        int r;
8,860✔
1005

1006
        if (!uid_is_valid(uid))
8,860✔
1007
                return -EINVAL;
1008

1009
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_UNSIGNED("uid", uid));
8,860✔
1010
        if (r < 0)
8,860✔
1011
                return r;
1012

1013
        r = query_append_uid_match(&query, match);
8,860✔
1014
        if (r < 0)
8,860✔
1015
                return r;
1016

1017
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
8,860✔
1018
        if (!iterator)
8,860✔
1019
                return -ENOMEM;
1020

1021
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
8,860✔
1022
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
8,860✔
1023
        if (r >= 0) {
8,860✔
1024
                r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
8,096✔
1025
                if (r == -ENOEXEC)
8,096✔
1026
                        return r;
1027
        }
1028
        if (r < 0) {
8,096✔
1029
                r = userdb_by_uid_fallbacks(uid, iterator, flags, &ur);
7,443✔
1030
                if (r < 0)
7,443✔
1031
                        return r;
1032
        }
1033

1034
        if (!user_record_match(ur, match))
5,261✔
1035
                return -ENOEXEC;
1036

1037
        if (ret)
5,258✔
1038
                *ret = TAKE_PTR(ur);
5,258✔
1039

1040
        return 0;
1041
}
1042

1043
int userdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
33✔
1044
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1045
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
33✔
1046
        int r, qr;
33✔
1047

1048
        assert(ret);
33✔
1049

1050
        r = query_append_uid_match(&query, match);
33✔
1051
        if (r < 0)
33✔
1052
                return r;
1053

1054
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
33✔
1055
        if (!iterator)
33✔
1056
                return -ENOMEM;
1057

1058
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ true, query, flags);
33✔
1059

1060
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
33✔
1061
                r = userdb_iterator_block_nss_systemd(iterator);
8✔
1062
                if (r < 0)
8✔
1063
                        return r;
1064

1065
                setpwent();
8✔
1066
                iterator->nss_iterating = true;
8✔
1067
        }
1068

1069
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
33✔
1070
                r = conf_files_list_nulstr(
20✔
1071
                                &iterator->dropins,
10✔
1072
                                ".user",
1073
                                NULL,
1074
                                CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1075
                                USERDB_DROPIN_DIR_NULSTR("userdb"));
1076
                if (r < 0)
10✔
1077
                        log_debug_errno(r, "Failed to find user drop-ins, ignoring: %m");
×
1078
        }
1079

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

1082
        /* propagate IPC error, but only if there are no drop-ins */
1083
        if (qr < 0 &&
33✔
1084
            !iterator->nss_iterating &&
3✔
1085
            strv_isempty(iterator->dropins))
35✔
1086
                return qr;
1087

1088
        *ret = TAKE_PTR(iterator);
33✔
1089
        return 0;
33✔
1090
}
1091

1092
static int userdb_iterator_get_one(UserDBIterator *iterator, UserRecord **ret) {
691✔
1093
        int r;
691✔
1094

1095
        assert(iterator);
691✔
1096
        assert(iterator->what == LOOKUP_USER);
691✔
1097

1098
        if (iterator->nss_iterating) {
691✔
1099
                struct passwd *pw;
208✔
1100

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

1104
                errno = 0;
208✔
1105
                pw = getpwent();
208✔
1106
                if (pw) {
208✔
1107
                        _cleanup_free_ char *buffer = NULL;
200✔
1108
                        bool incomplete = false;
200✔
1109
                        struct spwd spwd;
200✔
1110

1111
                        if (streq_ptr(pw->pw_name, "root"))
200✔
1112
                                iterator->synthesize_root = false;
8✔
1113
                        if (pw->pw_uid == UID_NOBODY)
200✔
1114
                                iterator->synthesize_nobody = false;
8✔
1115

1116
                        if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
200✔
1117
                                r = nss_spwd_for_passwd(pw, &spwd, &buffer);
200✔
1118
                                if (r < 0) {
200✔
1119
                                        log_debug_errno(r, "Failed to acquire shadow entry for user %s, ignoring: %m", pw->pw_name);
×
1120
                                        incomplete = ERRNO_IS_PRIVILEGE(r);
×
1121
                                }
1122
                        } else {
1123
                                r = -EUCLEAN;
1124
                                incomplete = true;
1125
                        }
1126

1127
                        r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
200✔
1128
                        if (r < 0)
200✔
1129
                                return r;
1130

1131
                        if (ret)
200✔
1132
                                (*ret)->incomplete = incomplete;
200✔
1133

1134
                        iterator->n_found++;
200✔
1135
                        return r;
200✔
1136
                }
1137

1138
                if (errno != 0)
8✔
1139
                        log_debug_errno(errno, "Failure to iterate NSS user database, ignoring: %m");
×
1140

1141
                iterator->nss_iterating = false;
8✔
1142
                endpwent();
8✔
1143
        }
1144

1145
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
507✔
1146
                const char *i = iterator->dropins[iterator->current_dropin];
32✔
1147
                _cleanup_free_ char *fn = NULL;
32✔
1148
                uid_t uid;
32✔
1149
                char *e;
32✔
1150

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

1153
                r = path_extract_filename(i, &fn);
32✔
1154
                if (r < 0)
32✔
1155
                        return r;
1156

1157
                e = endswith(fn, ".user"); /* not actually a .user file? Then skip to next */
32✔
1158
                if (!e)
32✔
1159
                        continue;
×
1160

1161
                *e = 0; /* Chop off suffix */
32✔
1162

1163
                if (parse_uid(fn, &uid) < 0) /* not a UID .user file? Then skip to next */
32✔
1164
                        continue;
16✔
1165

1166
                r = dropin_user_record_by_uid(uid, i, iterator->flags, ret);
16✔
1167
                if (r < 0) {
16✔
1168
                        log_debug_errno(r, "Failed to parse user record for UID " UID_FMT ", ignoring: %m", uid);
×
1169
                        continue; /* If we failed to parse this record, let's suppress it from enumeration,
×
1170
                                   * and continue with the next record. Maybe someone is dropping it files
1171
                                   * and only partially wrote this one. */
1172
                }
1173

1174
                iterator->current_dropin++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
16✔
1175
                iterator->n_found++;
16✔
1176
                return 0;
16✔
1177
        }
1178

1179
        /* Then, let's return the users provided by varlink IPC */
1180
        r = userdb_process(iterator, ret, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
475✔
1181
        if (r < 0) {
475✔
1182

1183
                /* Finally, synthesize root + nobody if not done yet */
1184
                if (iterator->synthesize_root) {
38✔
1185
                        iterator->synthesize_root = false;
3✔
1186
                        iterator->n_found++;
3✔
1187
                        return synthetic_root_user_build(ret);
3✔
1188
                }
1189

1190
                if (iterator->synthesize_nobody) {
35✔
1191
                        iterator->synthesize_nobody = false;
3✔
1192
                        iterator->n_found++;
3✔
1193
                        return synthetic_nobody_user_build(ret);
3✔
1194
                }
1195

1196
                /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1197
                if (iterator->n_found > 0)
32✔
1198
                        return -ESRCH;
32✔
1199
        }
1200

1201
        return r;
1202
}
1203

1204
int userdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, UserRecord **ret) {
576✔
1205
        int r;
576✔
1206

1207
        assert(iterator);
576✔
1208
        assert(iterator->what == LOOKUP_USER);
576✔
1209

1210
        for (;;) {
115✔
1211
                _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
576✔
1212

1213
                r = userdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &ur : NULL);
691✔
1214
                if (r < 0)
691✔
1215
                        return r;
1216

1217
                if (ur && !user_record_match(ur, match))
659✔
1218
                        continue;
115✔
1219

1220
                if (ret)
544✔
1221
                        *ret = TAKE_PTR(ur);
543✔
1222

1223
                return r;
1224
        }
1225
}
1226

1227
static int synthetic_root_group_build(GroupRecord **ret) {
5✔
1228
        return group_record_buildo(
5✔
1229
                        ret,
1230
                        SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
1231
                        SD_JSON_BUILD_PAIR_UNSIGNED("gid", 0),
1232
                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
1233
}
1234

1235
static int synthetic_nobody_group_build(GroupRecord **ret) {
4✔
1236
        return group_record_buildo(
4✔
1237
                        ret,
1238
                        SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME)),
1239
                        SD_JSON_BUILD_PAIR_UNSIGNED("gid", GID_NOBODY),
1240
                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
1241
}
1242

1243
static int synthetic_foreign_group_build(gid_t foreign_gid, GroupRecord **ret) {
18✔
1244
        assert(ret);
18✔
1245

1246
        if (!gid_is_valid(foreign_gid))
18✔
1247
                return -ESRCH;
18✔
1248
        if (foreign_gid > 0xFFFF)
18✔
1249
                return -ESRCH;
1250

1251
        _cleanup_free_ char *gn = NULL;
18✔
1252
        if (asprintf(&gn, "foreign-" GID_FMT, foreign_gid) < 0)
18✔
1253
                return -ENOMEM;
1254

1255
        _cleanup_free_ char *d = NULL;
18✔
1256
        if (asprintf(&d, "Foreign System Image GID " GID_FMT, foreign_gid) < 0)
18✔
1257
                return -ENOMEM;
1258

1259
        return group_record_buildo(
18✔
1260
                        ret,
1261
                        SD_JSON_BUILD_PAIR_STRING("groupName", gn),
1262
                        SD_JSON_BUILD_PAIR_STRING("description", d),
1263
                        SD_JSON_BUILD_PAIR_UNSIGNED("gid", FOREIGN_UID_BASE + foreign_gid),
1264
                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign")));
1265
}
1266

1267
static int synthetic_numeric_group_build(gid_t gid, GroupRecord **ret) {
2,832✔
1268
        assert(ret);
2,832✔
1269

1270
        if (gid == 0) /* This should be handled by synthetic_root_group_build() */
2,832✔
1271
                return -ESRCH;
2,832✔
1272

1273
        if (!gid_is_system(gid))
2,832✔
1274
                return -ESRCH;
1275

1276
        _cleanup_free_ char *gn = NULL;
2,829✔
1277
        if (asprintf(&gn, "unknown-" GID_FMT, gid) < 0)
2,829✔
1278
                return -ENOMEM;
1279

1280
        _cleanup_free_ char *d = NULL;
2,829✔
1281
        if (asprintf(&d, "Unknown System GID " UID_FMT, gid) < 0)
2,829✔
1282
                return -ENOMEM;
1283

1284
        return group_record_buildo(
2,829✔
1285
                        ret,
1286
                        SD_JSON_BUILD_PAIR_STRING("groupName", gn),
1287
                        SD_JSON_BUILD_PAIR_STRING("description", d),
1288
                        SD_JSON_BUILD_PAIR_UNSIGNED("gid", gid),
1289
                        SD_JSON_BUILD_PAIR_STRING("disposition", "system"));
1290
}
1291

1292
static int query_append_gid_match(sd_json_variant **query, const UserDBMatch *match) {
37,319✔
1293
        int r;
37,319✔
1294

1295
        assert(query);
37,319✔
1296

1297
        if (!userdb_match_is_set(match))
37,319✔
1298
                return 0;
1299

1300
        r = sd_json_variant_merge_objectbo(
9,632✔
1301
                        query,
1302
                        SD_JSON_BUILD_PAIR_CONDITION(match->gid_min > 0, "gidMin", SD_JSON_BUILD_UNSIGNED(match->gid_min)),
1303
                        SD_JSON_BUILD_PAIR_CONDITION(match->gid_max < GID_INVALID-1, "gidMax", SD_JSON_BUILD_UNSIGNED(match->gid_max)));
1304

1305
        if (r < 0)
9,632✔
1306
                return r;
1307

1308
        return query_append_common(query, match);
9,632✔
1309
}
1310

1311
static int groupdb_by_name_fallbacks(
25,262✔
1312
                const char *name,
1313
                UserDBIterator *iterator,
1314
                UserDBFlags flags,
1315
                GroupRecord **ret) {
1316

1317
        int r;
25,262✔
1318

1319
        assert(name);
25,262✔
1320
        assert(iterator);
25,262✔
1321
        assert(ret);
25,262✔
1322

1323
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
25,262✔
1324
                r = dropin_group_record_by_name(name, NULL, flags, ret);
12,860✔
1325
                if (r >= 0)
12,860✔
1326
                        return r;
1327
        }
1328

1329
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
25,236✔
1330
                r = userdb_iterator_block_nss_systemd(iterator);
2,026✔
1331
                if (r >= 0) {
2,026✔
1332
                        r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
2,026✔
1333
                        if (r >= 0)
2,026✔
1334
                                return r;
1335
                }
1336
        }
1337

1338
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
23,333✔
1339
                if (streq(name, "root"))
12,526✔
1340
                        return synthetic_root_group_build(ret);
1✔
1341

1342
                if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
12,525✔
1343
                        return synthetic_nobody_group_build(ret);
×
1344
        }
1345

1346
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
23,332✔
1347
                uid_t foreign_gid;
12,525✔
1348
                r = user_name_foreign_extract_uid(name, &foreign_gid); /* Same for UID + GID */
12,525✔
1349
                if (r < 0)
12,525✔
1350
                        return r;
4✔
1351
                if (r > 0)
12,525✔
1352
                        return synthetic_foreign_group_build(foreign_gid, ret);
4✔
1353
        }
1354

1355
        return -ESRCH;
1356
}
1357

1358
int groupdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
30,076✔
1359
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1360
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
30,076✔
1361
        int r;
30,076✔
1362

1363
        assert(name);
30,076✔
1364

1365
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
30,076✔
1366
                gid_t gid;
4,761✔
1367

1368
                if (parse_gid(name, &gid) >= 0)
4,761✔
1369
                        return groupdb_by_gid(gid, match, flags, ret);
3,016✔
1370
        }
1371

1372
        if (!valid_user_group_name(name, VALID_USER_RELAX))
27,060✔
1373
                return -EINVAL;
1374

1375
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_STRING("groupName", name));
27,055✔
1376
        if (r < 0)
27,055✔
1377
                return r;
1378

1379
        r = query_append_gid_match(&query, match);
27,055✔
1380
        if (r < 0)
27,055✔
1381
                return r;
1382

1383
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
27,055✔
1384
        if (!iterator)
27,055✔
1385
                return -ENOMEM;
1386

1387
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
27,055✔
1388
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
27,055✔
1389
        if (r >= 0) {
27,055✔
1390
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
16,240✔
1391
                if (r == -ENOEXEC)
16,240✔
1392
                        return r;
1393
        }
1394
        if (r < 0) {
16,240✔
1395
                r = groupdb_by_name_fallbacks(name, iterator, flags, &gr);
25,262✔
1396
                if (r < 0)
25,262✔
1397
                        return r;
1398
        }
1399

1400
        /* As above, we apply our own client-side filtering even if server-side filtering worked, for robustness and simplicity reasons. */
1401
        if (!group_record_match(gr, match))
3,727✔
1402
                return -ENOEXEC;
1403

1404
        if (ret)
3,724✔
1405
                *ret = TAKE_PTR(gr);
3,724✔
1406

1407
        return 0;
1408
}
1409

1410
static int groupdb_by_gid_fallbacks(
9,640✔
1411
                gid_t gid,
1412
                UserDBIterator *iterator,
1413
                UserDBFlags flags,
1414
                GroupRecord **ret) {
1415
        int r;
9,640✔
1416

1417
        assert(gid_is_valid(gid));
9,640✔
1418
        assert(iterator);
9,640✔
1419
        assert(ret);
9,640✔
1420

1421
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
9,640✔
1422
                r = dropin_group_record_by_gid(gid, NULL, flags, ret);
4,413✔
1423
                if (r >= 0)
4,413✔
1424
                        return r;
1425
        }
1426

1427
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
9,518✔
1428
                r = userdb_iterator_block_nss_systemd(iterator);
3,139✔
1429
                if (r >= 0) {
3,139✔
1430
                        r = nss_group_record_by_gid(gid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
3,139✔
1431
                        if (r >= 0)
3,139✔
1432
                                return r;
1433
                }
1434
        }
1435

1436
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
9,329✔
1437
                if (gid == 0)
8,148✔
1438
                        return synthetic_root_group_build(ret);
1✔
1439

1440
                if (gid == GID_NOBODY && synthesize_nobody())
8,147✔
1441
                        return synthetic_nobody_group_build(ret);
1✔
1442
        }
1443

1444
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && gid_is_foreign(gid))
9,327✔
1445
                return synthetic_foreign_group_build(gid - FOREIGN_UID_BASE, ret);
14✔
1446

1447
        if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
9,313✔
1448
                return synthetic_numeric_group_build(gid, ret);
2,832✔
1449

1450
        return -ESRCH;
1451
}
1452

1453
int groupdb_by_gid(gid_t gid, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
10,252✔
1454
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1455
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
10,252✔
1456
        int r;
10,252✔
1457

1458
        if (!gid_is_valid(gid))
10,252✔
1459
                return -EINVAL;
1460

1461
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_UNSIGNED("gid", gid));
10,252✔
1462
        if (r < 0)
10,252✔
1463
                return r;
1464

1465
        r = query_append_gid_match(&query, match);
10,252✔
1466
        if (r < 0)
10,252✔
1467
                return r;
1468

1469
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
10,252✔
1470
        if (!iterator)
10,252✔
1471
                return -ENOMEM;
1472

1473
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
10,252✔
1474
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
10,252✔
1475
        if (r >= 0) {
10,252✔
1476
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
8,927✔
1477
                if (r == -ENOEXEC)
8,927✔
1478
                        return r;
1479
        }
1480
        if (r < 0) {
8,927✔
1481
                r = groupdb_by_gid_fallbacks(gid, iterator, flags, &gr);
9,640✔
1482
                if (r < 0)
9,640✔
1483
                        return r;
1484
        }
1485

1486
        if (!group_record_match(gr, match))
3,768✔
1487
                return -ENOEXEC;
1488

1489
        if (ret)
3,765✔
1490
                *ret = TAKE_PTR(gr);
3,765✔
1491

1492
        return 0;
1493
}
1494

1495
int groupdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
12✔
1496
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1497
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
12✔
1498
        int r, qr;
12✔
1499

1500
        assert(ret);
12✔
1501

1502
        r = query_append_gid_match(&query, match);
12✔
1503
        if (r < 0)
12✔
1504
                return r;
1505

1506
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
12✔
1507
        if (!iterator)
12✔
1508
                return -ENOMEM;
1509

1510
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ true, query, flags);
12✔
1511

1512
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
12✔
1513
                r = userdb_iterator_block_nss_systemd(iterator);
2✔
1514
                if (r < 0)
2✔
1515
                        return r;
1516

1517
                setgrent();
2✔
1518
                iterator->nss_iterating = true;
2✔
1519
        }
1520

1521
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
12✔
1522
                r = conf_files_list_nulstr(
4✔
1523
                                &iterator->dropins,
2✔
1524
                                ".group",
1525
                                NULL,
1526
                                CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1527
                                USERDB_DROPIN_DIR_NULSTR("userdb"));
1528
                if (r < 0)
2✔
1529
                        log_debug_errno(r, "Failed to find group drop-ins, ignoring: %m");
×
1530
        }
1531

1532
        if (qr < 0 &&
12✔
1533
            !iterator->nss_iterating &&
×
1534
            strv_isempty(iterator->dropins))
12✔
1535
                return qr;
1536

1537
        *ret = TAKE_PTR(iterator);
12✔
1538
        return 0;
12✔
1539
}
1540

1541
static int groupdb_iterator_get_one(UserDBIterator *iterator, GroupRecord **ret) {
558✔
1542
        int r;
558✔
1543

1544
        assert(iterator);
558✔
1545
        assert(iterator->what == LOOKUP_GROUP);
558✔
1546

1547
        if (iterator->nss_iterating) {
558✔
1548
                struct group *gr;
118✔
1549

1550
                errno = 0;
118✔
1551
                gr = getgrent();
118✔
1552
                if (gr) {
118✔
1553
                        _cleanup_free_ char *buffer = NULL;
116✔
1554
                        bool incomplete = false;
116✔
1555
                        struct sgrp sgrp;
116✔
1556

1557
                        if (streq_ptr(gr->gr_name, "root"))
116✔
1558
                                iterator->synthesize_root = false;
2✔
1559
                        if (gr->gr_gid == GID_NOBODY)
116✔
1560
                                iterator->synthesize_nobody = false;
2✔
1561

1562
                        if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
116✔
1563
                                r = nss_sgrp_for_group(gr, &sgrp, &buffer);
116✔
1564
                                if (r < 0) {
116✔
1565
                                        log_debug_errno(r, "Failed to acquire shadow entry for group %s, ignoring: %m", gr->gr_name);
×
1566
                                        incomplete = ERRNO_IS_PRIVILEGE(r);
×
1567
                                }
1568
                        } else {
1569
                                r = -EUCLEAN;
1570
                                incomplete = true;
1571
                        }
1572

1573
                        r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
116✔
1574
                        if (r < 0)
116✔
1575
                                return r;
1576

1577
                        if (ret)
116✔
1578
                                (*ret)->incomplete = incomplete;
116✔
1579

1580
                        iterator->n_found++;
116✔
1581
                        return r;
116✔
1582
                }
1583

1584
                if (errno != 0)
2✔
1585
                        log_debug_errno(errno, "Failure to iterate NSS group database, ignoring: %m");
×
1586

1587
                iterator->nss_iterating = false;
2✔
1588
                endgrent();
2✔
1589
        }
1590

1591
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
445✔
1592
                const char *i = iterator->dropins[iterator->current_dropin];
6✔
1593
                _cleanup_free_ char *fn = NULL;
6✔
1594
                gid_t gid;
6✔
1595
                char *e;
6✔
1596

1597
                r = path_extract_filename(i, &fn);
6✔
1598
                if (r < 0)
6✔
1599
                        return r;
1600

1601
                e = endswith(fn, ".group");
6✔
1602
                if (!e)
6✔
1603
                        continue;
×
1604

1605
                *e = 0; /* Chop off suffix */
6✔
1606

1607
                if (parse_gid(fn, &gid) < 0)
6✔
1608
                        continue;
3✔
1609

1610
                r = dropin_group_record_by_gid(gid, i, iterator->flags, ret);
3✔
1611
                if (r < 0) {
3✔
1612
                        log_debug_errno(r, "Failed to parse group record for GID " GID_FMT ", ignoring: %m", gid);
×
1613
                        continue;
×
1614
                }
1615

1616
                iterator->current_dropin++;
3✔
1617
                iterator->n_found++;
3✔
1618
                return 0;
3✔
1619
        }
1620

1621
        r = userdb_process(iterator, NULL, ret, NULL, NULL);
439✔
1622
        if (r < 0) {
439✔
1623
                if (iterator->synthesize_root) {
18✔
1624
                        iterator->synthesize_root = false;
3✔
1625
                        iterator->n_found++;
3✔
1626
                        return synthetic_root_group_build(ret);
3✔
1627
                }
1628

1629
                if (iterator->synthesize_nobody) {
15✔
1630
                        iterator->synthesize_nobody = false;
3✔
1631
                        iterator->n_found++;
3✔
1632
                        return synthetic_nobody_group_build(ret);
3✔
1633
                }
1634

1635
                /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1636
                if (iterator->n_found > 0)
12✔
1637
                        return -ESRCH;
12✔
1638
        }
1639

1640
        return r;
1641
}
1642

1643
int groupdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, GroupRecord **ret) {
429✔
1644
        int r;
429✔
1645

1646
        assert(iterator);
429✔
1647
        assert(iterator->what == LOOKUP_GROUP);
429✔
1648

1649
        for (;;) {
129✔
1650
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
429✔
1651

1652
                r = groupdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &gr : NULL);
558✔
1653
                if (r < 0)
558✔
1654
                        return r;
1655

1656
                if (gr && !group_record_match(gr, match))
546✔
1657
                        continue;
129✔
1658

1659
                if (ret)
417✔
1660
                        *ret = TAKE_PTR(gr);
417✔
1661

1662
                return r;
1663
        }
1664
}
1665

1666
static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
13,482✔
1667
        int r;
13,482✔
1668

1669
        r = conf_files_list_nulstr(
13,482✔
1670
                        &i->dropins,
1671
                        ".membership",
1672
                        NULL,
1673
                        CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED_BY_SYMLINK,
1674
                        USERDB_DROPIN_DIR_NULSTR("userdb"));
1675
        if (r < 0)
13,482✔
1676
                log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
×
1677
}
13,482✔
1678

1679
int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1,863✔
1680
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1681
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
1,863✔
1682
        int r, qr;
1,863✔
1683

1684
        assert(ret);
1,863✔
1685

1686
        if (!valid_user_group_name(name, VALID_USER_RELAX))
1,863✔
1687
                return -EINVAL;
1688

1689
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_STRING("userName", name));
1,861✔
1690
        if (r < 0)
1,861✔
1691
                return r;
1692

1693
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1,861✔
1694
        if (!iterator)
1,861✔
1695
                return -ENOMEM;
1696

1697
        iterator->filter_user_name = strdup(name);
1,861✔
1698
        if (!iterator->filter_user_name)
1,861✔
1699
                return -ENOMEM;
1700

1701
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1,861✔
1702

1703
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1,861✔
1704
                r = userdb_iterator_block_nss_systemd(iterator);
103✔
1705
                if (r < 0)
103✔
1706
                        return r;
1707

1708
                setgrent();
103✔
1709
                iterator->nss_iterating = true;
103✔
1710
        }
1711

1712
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1,861✔
1713
                discover_membership_dropins(iterator, flags);
1,611✔
1714

1715
        if (qr < 0 &&
1,861✔
1716
            !iterator->nss_iterating &&
1,508✔
1717
            strv_isempty(iterator->dropins))
1,863✔
1718
                return qr;
1719

1720
        *ret = TAKE_PTR(iterator);
353✔
1721
        return 0;
353✔
1722
}
1723

1724
int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
26,471✔
1725
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1726
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
26,471✔
1727
        int r, qr;
26,471✔
1728

1729
        assert(ret);
26,471✔
1730

1731
        if (!valid_user_group_name(name, VALID_USER_RELAX))
26,471✔
1732
                return -EINVAL;
1733

1734
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR_STRING("groupName", name));
26,469✔
1735
        if (r < 0)
26,469✔
1736
                return r;
1737

1738
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
26,469✔
1739
        if (!iterator)
26,469✔
1740
                return -ENOMEM;
1741

1742
        iterator->filter_group_name = strdup(name);
26,469✔
1743
        if (!iterator->filter_group_name)
26,469✔
1744
                return -ENOMEM;
1745

1746
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
26,469✔
1747

1748
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
26,469✔
UNCOV
1749
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
×
1750

UNCOV
1751
                r = userdb_iterator_block_nss_systemd(iterator);
×
UNCOV
1752
                if (r < 0)
×
1753
                        return r;
1754

1755
                /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
UNCOV
1756
                (void) nss_group_record_by_name(name, false, &gr);
×
UNCOV
1757
                if (gr) {
×
UNCOV
1758
                        iterator->members_of_group = strv_copy(gr->members);
×
UNCOV
1759
                        if (!iterator->members_of_group)
×
1760
                                return -ENOMEM;
1761

UNCOV
1762
                        iterator->index_members_of_group = 0;
×
1763

UNCOV
1764
                        iterator->found_group_name = strdup(name);
×
UNCOV
1765
                        if (!iterator->found_group_name)
×
1766
                                return -ENOMEM;
1767
                }
1768
        }
1769

1770
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
26,469✔
1771
                discover_membership_dropins(iterator, flags);
11,871✔
1772

1773
        if (qr < 0 &&
26,469✔
1774
            strv_isempty(iterator->members_of_group) &&
11,870✔
1775
            strv_isempty(iterator->dropins))
26,471✔
1776
                return qr;
1777

1778
        *ret = TAKE_PTR(iterator);
14,599✔
1779
        return 0;
14,599✔
1780
}
1781

1782
int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
2✔
1783
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1784
        int r, qr;
2✔
1785

1786
        assert(ret);
2✔
1787

1788
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
2✔
1789
        if (!iterator)
2✔
1790
                return -ENOMEM;
1791

1792
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
2✔
1793

1794
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
2✔
1795
                r = userdb_iterator_block_nss_systemd(iterator);
×
1796
                if (r < 0)
×
1797
                        return r;
1798

1799
                setgrent();
×
1800
                iterator->nss_iterating = true;
×
1801
        }
1802

1803
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
2✔
1804
                discover_membership_dropins(iterator, flags);
×
1805

1806
        if (qr < 0 &&
2✔
1807
            !iterator->nss_iterating &&
×
1808
            strv_isempty(iterator->dropins))
2✔
1809
                return qr;
1810

1811
        *ret = TAKE_PTR(iterator);
2✔
1812
        return 0;
2✔
1813
}
1814

1815
int membershipdb_iterator_get(
15,192✔
1816
                UserDBIterator *iterator,
1817
                char **ret_user,
1818
                char **ret_group) {
1819

1820
        int r;
15,192✔
1821

1822
        assert(iterator);
15,192✔
1823

1824
        for (;;) {
15,196✔
1825
                /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1826
                if (!iterator->members_of_group) {
15,194✔
1827
                        struct group *g;
15,192✔
1828

1829
                        if (!iterator->nss_iterating)
15,192✔
1830
                                break;
1831

1832
                        assert(!iterator->found_user_name);
105✔
1833
                        do {
6,077✔
1834
                                errno = 0;
6,077✔
1835
                                g = getgrent();
6,077✔
1836
                                if (!g) {
6,077✔
1837
                                        if (errno != 0)
103✔
1838
                                                log_debug_errno(errno, "Failure during NSS group iteration, ignoring: %m");
×
1839
                                        break;
1840
                                }
1841

1842
                        } while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
5,974✔
1843
                                                              strv_isempty(g->gr_mem));
×
1844

1845
                        if (g) {
105✔
1846
                                r = free_and_strdup(&iterator->found_group_name, g->gr_name);
2✔
1847
                                if (r < 0)
2✔
1848
                                        return r;
1849

1850
                                if (iterator->filter_user_name)
2✔
1851
                                        iterator->members_of_group = strv_new(iterator->filter_user_name);
2✔
1852
                                else
1853
                                        iterator->members_of_group = strv_copy(g->gr_mem);
×
1854
                                if (!iterator->members_of_group)
2✔
1855
                                        return -ENOMEM;
1856

1857
                                iterator->index_members_of_group = 0;
2✔
1858
                        } else {
1859
                                iterator->nss_iterating = false;
103✔
1860
                                endgrent();
103✔
1861
                                break;
103✔
1862
                        }
1863
                }
1864

1865
                assert(iterator->found_group_name);
4✔
1866
                assert(iterator->members_of_group);
4✔
1867
                assert(!iterator->found_user_name);
4✔
1868

1869
                if (iterator->members_of_group[iterator->index_members_of_group]) {
4✔
1870
                        _cleanup_free_ char *cu = NULL, *cg = NULL;
2✔
1871

1872
                        if (ret_user) {
2✔
1873
                                cu = strdup(iterator->members_of_group[iterator->index_members_of_group]);
2✔
1874
                                if (!cu)
2✔
1875
                                        return -ENOMEM;
1876
                        }
1877

1878
                        if (ret_group) {
2✔
1879
                                cg = strdup(iterator->found_group_name);
2✔
1880
                                if (!cg)
2✔
1881
                                        return -ENOMEM;
1882
                        }
1883

1884
                        if (ret_user)
2✔
1885
                                *ret_user = TAKE_PTR(cu);
2✔
1886

1887
                        if (ret_group)
2✔
1888
                                *ret_group = TAKE_PTR(cg);
2✔
1889

1890
                        iterator->index_members_of_group++;
2✔
1891
                        return 0;
2✔
1892
                }
1893

1894
                iterator->members_of_group = strv_free(iterator->members_of_group);
2✔
1895
                iterator->found_group_name = mfree(iterator->found_group_name);
2✔
1896
        }
1897

1898
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
15,190✔
1899
                const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
×
1900
                _cleanup_free_ char *un = NULL, *gn = NULL;
×
1901

1902
                e = endswith(i, ".membership");
×
1903
                if (!e)
×
1904
                        continue;
×
1905

1906
                c = memchr(i, ':', e - i);
×
1907
                if (!c)
×
1908
                        continue;
×
1909

1910
                un = strndup(i, c - i);
×
1911
                if (!un)
×
1912
                        return -ENOMEM;
1913
                if (iterator->filter_user_name) {
×
1914
                        if (!streq(un, iterator->filter_user_name))
×
1915
                                continue;
×
1916
                } else if (!valid_user_group_name(un, VALID_USER_RELAX))
×
1917
                        continue;
×
1918

1919
                c++; /* skip over ':' */
×
1920
                gn = strndup(c, e - c);
×
1921
                if (!gn)
×
1922
                        return -ENOMEM;
1923
                if (iterator->filter_group_name) {
×
1924
                        if (!streq(gn, iterator->filter_group_name))
×
1925
                                continue;
×
1926
                } else if (!valid_user_group_name(gn, VALID_USER_RELAX))
×
1927
                        continue;
×
1928

1929
                iterator->current_dropin++;
×
1930
                iterator->n_found++;
×
1931

1932
                if (ret_user)
×
1933
                        *ret_user = TAKE_PTR(un);
×
1934
                if (ret_group)
×
1935
                        *ret_group = TAKE_PTR(gn);
×
1936

1937
                return 0;
1938
        }
1939

1940
        r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
15,190✔
1941
        if (r < 0 && iterator->n_found > 0)
15,190✔
1942
                return -ESRCH;
226✔
1943

1944
        return r;
1945
}
1946

1947
int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret) {
14,574✔
1948
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
14,574✔
1949
        _cleanup_strv_free_ char **members = NULL;
14,574✔
1950
        int r;
14,574✔
1951

1952
        assert(name);
14,574✔
1953
        assert(ret);
14,574✔
1954

1955
        r = membershipdb_by_group(name, flags, &iterator);
14,574✔
1956
        if (r < 0)
14,574✔
1957
                return r;
1958

1959
        for (;;) {
14,611✔
1960
                _cleanup_free_ char *user_name = NULL;
19✔
1961

1962
                r = membershipdb_iterator_get(iterator, &user_name, NULL);
14,592✔
1963
                if (r == -ESRCH)
14,592✔
1964
                        break;
1965
                if (r < 0)
19✔
1966
                        return r;
1967

1968
                r = strv_consume(&members, TAKE_PTR(user_name));
19✔
1969
                if (r < 0)
19✔
1970
                        return r;
1971
        }
1972

1973
        strv_sort_uniq(members);
14,573✔
1974

1975
        *ret = TAKE_PTR(members);
14,573✔
1976
        return 0;
14,573✔
1977
}
1978

1979
int userdb_block_nss_systemd(int b) {
18,472✔
1980
        int r;
18,472✔
1981

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

1984
        _cleanup_(dlclosep) void *dl = NULL;
18,472✔
1985
        const char *dle;
18,472✔
1986
        r = dlopen_safe(LIBDIR "/libnss_systemd.so.2", &dl, &dle);
18,472✔
1987
        if (r < 0) {
18,472✔
1988
                /* If the file isn't installed, don't complain loudly */
1989
                log_debug_errno(r, "Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dle ?: STRERROR(r));
×
1990
                return 0;
×
1991
        }
1992

1993
        log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2");
18,472✔
1994

1995
        int (*call)(bool b) = dlsym(dl, "_nss_systemd_block");
18,472✔
1996
        if (!call)
18,472✔
1997
                /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1998
                return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
×
1999
                                       "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());
2000

2001
        return call(b);
18,472✔
2002
}
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