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

systemd / systemd / 15313160050

28 May 2025 09:01PM UTC coverage: 72.04% (+0.06%) from 71.981%
15313160050

push

github

poettering
update NEWS

299462 of 415690 relevant lines covered (72.04%)

702804.32 hits per line

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

88.38
/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);
1,376✔
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) {
75,219✔
72
        if (!iterator)
75,219✔
73
                return NULL;
74

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

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

80
        switch (iterator->what) {
75,066✔
81

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

85
                if (iterator->nss_iterating)
11,267✔
86
                        endpwent();
×
87

88
                break;
89

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

93
                if (iterator->nss_iterating)
36,414✔
94
                        endgrent();
×
95

96
                break;
97

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

105
                if (iterator->nss_iterating)
27,385✔
106
                        endgrent();
×
107

108
                break;
109

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

114
        sd_event_unref(iterator->event);
75,066✔
115

116
        if (iterator->nss_systemd_blocked)
75,066✔
117
                assert_se(userdb_block_nss_systemd(false) >= 0);
8,769✔
118

119
        return mfree(iterator);
75,066✔
120
}
121

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

125
        assert(what >= 0);
75,066✔
126
        assert(what < _LOOKUP_WHAT_MAX);
75,066✔
127

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

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

139
        return i;
75,066✔
140
}
141

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

145
        assert(iterator);
8,769✔
146

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

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

154
        iterator->nss_systemd_blocked = true;
8,769✔
155
        return 1;
8,769✔
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) {
5,016✔
164
        sd_json_variant_unref(d->record);
5,016✔
165
}
5,016✔
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) {
35✔
173
        free(d->user_name);
35✔
174
        free(d->group_name);
35✔
175
}
35✔
176

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

183
        int r;
147,763✔
184

185
        assert(iterator);
147,763✔
186
        assert(link);
147,763✔
187
        assert(error_id);
147,763✔
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;
992,705✔
205
        STRV_FOREACH(f, fields) {
992,705✔
206
                if (!sd_varlink_error_is_invalid_parameter(error_id, parameters, *f))
867,816✔
207
                        continue;
844,942✔
208

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

212
                restart = true;
213
                break;
214
        }
215

216
        if (!restart)
147,763✔
217
                return 0;
147,763✔
218

219
        /* Now patch the fields out */
220
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query =
147,763✔
221
                sd_json_variant_ref(iterator->query);
22,874✔
222

223
        r = sd_json_variant_filter(&patched_query, (char**const) fields);
22,874✔
224
        if (r < 0)
22,874✔
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(
68,622✔
229
                        iterator,
230
                        ASSERT_PTR(sd_varlink_get_description(link)),
22,874✔
231
                        iterator->method,
232
                        iterator->more,
22,874✔
233
                        patched_query);
234
        if (r < 0)
22,874✔
235
                return r;
236

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

241
static int userdb_on_query_reply(
152,814✔
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);
152,814✔
249
        int r;
152,814✔
250

251
        if (error_id) {
152,814✔
252
                log_debug("Got lookup error: %s", error_id);
147,763✔
253

254
                r = userdb_maybe_restart_query(iterator, link, parameters, error_id);
147,763✔
255
                if (r < 0)
147,763✔
256
                        return r;
257
                if (r > 0) {
147,763✔
258
                        r = 0;
22,874✔
259
                        goto finish;
22,874✔
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,
124,889✔
267
                               "io.systemd.UserDatabase.NoRecordFound",
268
                               "io.systemd.UserDatabase.ConflictingRecordFound") ||
42✔
269
                    sd_varlink_error_is_invalid_parameter(error_id, parameters, "userName") ||
84✔
270
                    sd_varlink_error_is_invalid_parameter(error_id, parameters, "groupName"))
42✔
271
                        r = -ESRCH;
124,847✔
272
                else if (streq(error_id, "io.systemd.UserDatabase.NonMatchingRecordFound"))
42✔
273
                        r = -ENOEXEC;
274
                else if (streq(error_id, "io.systemd.UserDatabase.ServiceNotAvailable"))
30✔
275
                        r = -EHOSTDOWN;
276
                else if (streq(error_id, "io.systemd.UserDatabase.EnumerationNotSupported"))
30✔
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;
124,889✔
284
        }
285

286
        switch (iterator->what) {
5,051✔
287

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

291
                static const sd_json_dispatch_field dispatch_table[] = {
2,521✔
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,223✔
297

298
                assert_se(!iterator->found_user);
2,521✔
299

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

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

309
                hr = user_record_new();
2,521✔
310
                if (!hr) {
2,521✔
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);
2,521✔
316
                if (r < 0)
2,521✔
317
                        goto finish;
×
318

319
                if (!hr->service) {
2,521✔
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;
2,521✔
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"))
2,521✔
329
                        iterator->synthesize_root = false;
501✔
330
                if (hr->uid == UID_NOBODY)
2,521✔
331
                        iterator->synthesize_nobody = false;
15✔
332

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

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

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

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

348
                static const sd_json_dispatch_field dispatch_table[] = {
2,495✔
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,270✔
354

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

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

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

366
                g = group_record_new();
2,495✔
367
                if (!g) {
2,495✔
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,495✔
373
                if (r < 0)
2,495✔
374
                        goto finish;
×
375

376
                if (!g->service) {
2,495✔
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,495✔
382

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

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

391
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
2,495✔
392
                        return 0;
225✔
393

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

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

401
                static const sd_json_dispatch_field dispatch_table[] = {
35✔
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);
35✔
408
                assert(!iterator->found_group_name);
35✔
409

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

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

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

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

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

429
finish:
4,518✔
430
        /* If we got one ESRCH or ENOEXEC, let that win. This way when we do a wild dump we won't be tripped
431
         * up by bad errors – as long as at least one connection ended somewhat cleanly */
432
        if (IN_SET(r, -ESRCH, -ENOEXEC) || iterator->error == 0)
152,281✔
433
                iterator->error = -r;
146,434✔
434

435
        assert_se(set_remove(iterator->links, link) == link);
152,281✔
436
        link = sd_varlink_unref(link);
152,281✔
437
        return 0;
152,281✔
438
}
439

440
static int userdb_connect(
153,665✔
441
                UserDBIterator *iterator,
442
                const char *path,
443
                const char *method,
444
                bool more,
445
                sd_json_variant *query) {
446

447
        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
153,665✔
448
        int r;
153,665✔
449

450
        assert(iterator);
153,665✔
451
        assert(path);
153,665✔
452
        assert(method);
153,665✔
453

454
        r = sd_varlink_connect_address(&vl, path);
153,665✔
455
        if (r < 0)
153,665✔
456
                return log_debug_errno(r, "Unable to connect to %s: %m", path);
8✔
457

458
        sd_varlink_set_userdata(vl, iterator);
153,657✔
459

460
        if (!iterator->event) {
153,657✔
461
                r = sd_event_new(&iterator->event);
49,218✔
462
                if (r < 0)
49,218✔
463
                        return log_debug_errno(r, "Unable to allocate event loop: %m");
×
464
        }
465

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

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

477
        r = sd_varlink_bind_reply(vl, userdb_on_query_reply);
153,657✔
478
        if (r < 0)
153,657✔
479
                return log_debug_errno(r, "Failed to bind reply callback: %m");
×
480

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

487
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query = sd_json_variant_ref(query);
307,314✔
488
        r = sd_json_variant_set_field_string(&patched_query, "service", service);
153,657✔
489
        if (r < 0)
153,657✔
490
                return log_debug_errno(r, "Unable to set service JSON field: %m");
×
491

492
        if (more)
153,657✔
493
                r = sd_varlink_observe(vl, method, patched_query);
45,543✔
494
        else
495
                r = sd_varlink_invoke(vl, method, patched_query);
108,114✔
496
        if (r < 0)
153,657✔
497
                return log_debug_errno(r, "Failed to invoke varlink method: %m");
×
498

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

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

512
        _cleanup_strv_free_ char **except = NULL, **only = NULL;
75,066✔
513
        _cleanup_closedir_ DIR *d = NULL;
75,066✔
514
        const char *e;
75,066✔
515
        int r, ret = 0;
75,066✔
516

517
        assert(iterator);
75,066✔
518
        assert(method);
75,066✔
519

520
        if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
75,066✔
521
                return -ENOLINK;
522

523
        assert(!iterator->query);
49,226✔
524
        iterator->method = method; /* note: we don't make a copy here! */
49,226✔
525
        iterator->query = sd_json_variant_ref(query);
49,226✔
526
        iterator->more = more;
49,226✔
527

528
        e = getenv("SYSTEMD_BYPASS_USERDB");
49,226✔
529
        if (e) {
49,226✔
530
                r = parse_boolean(e);
9,236✔
531
                if (r > 0)
9,236✔
532
                        return -ENOLINK;
533
                if (r < 0) {
9,236✔
534
                        except = strv_split(e, ":");
9,236✔
535
                        if (!except)
9,236✔
536
                                return -ENOMEM;
537
                }
538
        }
539

540
        e = getenv("SYSTEMD_ONLY_USERDB");
49,226✔
541
        if (e) {
49,226✔
542
                only = strv_split(e, ":");
×
543
                if (!only)
×
544
                        return -ENOMEM;
545
        }
546

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

559
        d = opendir("/run/systemd/userdb/");
39,497✔
560
        if (!d) {
39,497✔
561
                if (errno == ENOENT)
×
562
                        return -ESRCH;
563

564
                return -errno;
×
565
        }
566

567
        FOREACH_DIRENT(de, d, return -errno) {
327,757✔
568
                _cleanup_free_ char *p = NULL;
121,062✔
569
                bool is_nss, is_dropin;
209,266✔
570

571
                if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
209,266✔
572
                        continue;
39,489✔
573

574
                if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
169,777✔
575
                    streq(de->d_name, "io.systemd.DynamicUser"))
30✔
576
                        continue;
6✔
577

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

586
                /* Similar for the drop-in service */
587
                is_dropin = streq(de->d_name, "io.systemd.DropIn");
130,314✔
588
                if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
130,314✔
589
                        continue;
9,152✔
590

591
                if (strv_contains(except, de->d_name))
121,162✔
592
                        continue;
100✔
593

594
                if (only && !strv_contains(only, de->d_name))
121,062✔
595
                        continue;
×
596

597
                p = path_join("/run/systemd/userdb/", de->d_name);
121,062✔
598
                if (!p)
121,062✔
599
                        return -ENOMEM;
×
600

601
                r = userdb_connect(iterator, p, method, more, query);
121,062✔
602
                if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
121,062✔
603
                                       * and could connect to it */
604
                        iterator->nss_covered = true;
32✔
605
                if (is_dropin && r >= 0)
121,062✔
606
                        iterator->dropin_covered = true;
30,337✔
607

608
                if (ret == 0 && r < 0)
121,062✔
609
                        ret = r;
8✔
610
        }
611

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

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

619
static int userdb_process(
49,825✔
620
                UserDBIterator *iterator,
621
                UserRecord **ret_user_record,
622
                GroupRecord **ret_group_record,
623
                char **ret_user_name,
624
                char **ret_group_name) {
625

626
        int r;
49,825✔
627

628
        assert(iterator);
49,825✔
629

630
        for (;;) {
784,832✔
631
                if (iterator->what == LOOKUP_USER && iterator->found_user) {
784,832✔
632
                        if (ret_user_record)
2,521✔
633
                                *ret_user_record = TAKE_PTR(iterator->found_user);
2,521✔
634
                        else
635
                                iterator->found_user = user_record_unref(iterator->found_user);
×
636

637
                        if (ret_group_record)
2,521✔
638
                                *ret_group_record = NULL;
×
639
                        if (ret_user_name)
2,521✔
640
                                *ret_user_name = NULL;
×
641
                        if (ret_group_name)
2,521✔
642
                                *ret_group_name = NULL;
×
643

644
                        return 0;
2,521✔
645
                }
646

647
                if (iterator->what == LOOKUP_GROUP && iterator->found_group) {
782,311✔
648
                        if (ret_group_record)
2,495✔
649
                                *ret_group_record = TAKE_PTR(iterator->found_group);
2,495✔
650
                        else
651
                                iterator->found_group = group_record_unref(iterator->found_group);
×
652

653
                        if (ret_user_record)
2,495✔
654
                                *ret_user_record = NULL;
×
655
                        if (ret_user_name)
2,495✔
656
                                *ret_user_name = NULL;
×
657
                        if (ret_group_name)
2,495✔
658
                                *ret_group_name = NULL;
×
659

660
                        return 0;
2,495✔
661
                }
662

663
                if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
779,816✔
664
                        if (ret_user_name)
35✔
665
                                *ret_user_name = TAKE_PTR(iterator->found_user_name);
21✔
666
                        else
667
                                iterator->found_user_name = mfree(iterator->found_user_name);
14✔
668

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

674
                        if (ret_user_record)
35✔
675
                                *ret_user_record = NULL;
×
676
                        if (ret_group_record)
35✔
677
                                *ret_group_record = NULL;
×
678

679
                        return 0;
35✔
680
                }
681

682
                if (set_isempty(iterator->links)) {
779,781✔
683
                        if (iterator->error == 0)
44,774✔
684
                                return -ESRCH;
685

686
                        return -abs(iterator->error);
44,717✔
687
                }
688

689
                if (!iterator->event)
735,007✔
690
                        return -ESRCH;
691

692
                r = sd_event_run(iterator->event, UINT64_MAX);
735,007✔
693
                if (r < 0)
735,007✔
694
                        return r;
695
        }
696
}
697

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

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

719
static int synthetic_foreign_user_build(uid_t foreign_uid, UserRecord **ret) {
11✔
720
        assert(ret);
11✔
721

722
        if (!uid_is_valid(foreign_uid))
11✔
723
                return -ESRCH;
11✔
724
        if (foreign_uid > 0xFFFF)
10✔
725
                return -ESRCH;
726

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

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

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

746
static int synthetic_numeric_user_build(uid_t uid, UserRecord **ret) {
2,938✔
747
        assert(ret);
2,938✔
748

749
        if (uid == 0) /* This should be handled by synthetic_root_user_build() */
2,938✔
750
                return -ESRCH;
2,938✔
751

752
        if (!uid_is_system(uid))
2,937✔
753
                return -ESRCH;
754

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

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

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

771
static int user_name_foreign_extract_uid(const char *name, uid_t *ret_uid) {
12,965✔
772
        int r;
12,965✔
773

774
        assert(name);
12,965✔
775
        assert(ret_uid);
12,965✔
776

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

780
        const char *e = startswith(name, "foreign-");
12,965✔
781
        if (!e)
12,965✔
782
                goto nomatch;
12,958✔
783

784
        uid_t uid;
7✔
785
        r = parse_uid(e, &uid);
7✔
786
        if (r < 0)
7✔
787
                goto nomatch;
2✔
788

789
        if (uid > 0xFFFF)
5✔
790
                goto nomatch;
1✔
791

792
        *ret_uid = uid;
4✔
793
        return 1;
4✔
794

795
nomatch:
12,961✔
796
        *ret_uid = UID_INVALID;
12,961✔
797
        return 0;
12,961✔
798
}
799

800
static int query_append_disposition_mask(sd_json_variant **query, uint64_t mask) {
15,797✔
801
        int r;
15,797✔
802

803
        assert(query);
15,797✔
804

805
        if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL))
15,797✔
806
                return 0;
15,797✔
807

808
        _cleanup_strv_free_ char **dispositions = NULL;
15,789✔
809
        for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
126,312✔
810
                if (!BITS_SET(mask, d))
221,046✔
811
                        continue;
78,954✔
812

813
                r = strv_extend(&dispositions, user_disposition_to_string(d));
31,569✔
814
                if (r < 0)
31,569✔
815
                        return r;
816
        }
817

818
        return sd_json_variant_merge_objectbo(
15,789✔
819
                        query,
820
                        SD_JSON_BUILD_PAIR_STRV("dispositionMask", dispositions));
821
}
822

823
static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *match) {
11,267✔
824
        int r;
11,267✔
825

826
        assert(query);
11,267✔
827

828
        if (!userdb_match_is_set(match))
11,267✔
829
                return 0;
830

831
        r = sd_json_variant_merge_objectbo(
6,256✔
832
                        query,
833
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
834
                        SD_JSON_BUILD_PAIR_CONDITION(match->uid_min > 0, "uidMin", SD_JSON_BUILD_UNSIGNED(match->uid_min)),
835
                        SD_JSON_BUILD_PAIR_CONDITION(match->uid_max < UID_INVALID-1, "uidMax", SD_JSON_BUILD_UNSIGNED(match->uid_max)));
836
        if (r < 0)
6,254✔
837
                return r;
838

839
        return query_append_disposition_mask(query, match->disposition_mask);
6,254✔
840
}
841

842
static int userdb_by_name_fallbacks(
2,089✔
843
                const char *name,
844
                UserDBIterator *iterator,
845
                UserDBFlags flags,
846
                UserRecord **ret) {
847
        int r;
2,089✔
848

849
        assert(name);
2,089✔
850
        assert(iterator);
2,089✔
851
        assert(ret);
2,089✔
852

853
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
2,089✔
854
                r = dropin_user_record_by_name(name, /* path= */ NULL, flags, ret);
1,915✔
855
                if (r >= 0)
1,915✔
856
                        return r;
857
        }
858

859
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
1,021✔
860
                /* Make sure the NSS lookup doesn't recurse back to us. */
861

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

871
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
784✔
872
                if (streq(name, "root"))
279✔
873
                        return synthetic_root_user_build(ret);
1✔
874

875
                if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
278✔
876
                        return synthetic_nobody_user_build(ret);
×
877
        }
878

879
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
783✔
880
                uid_t foreign_uid;
278✔
881
                r = user_name_foreign_extract_uid(name, &foreign_uid);
278✔
882
                if (r < 0)
278✔
883
                        return r;
4✔
884
                if (r > 0)
278✔
885
                        return synthetic_foreign_user_build(foreign_uid, ret);
4✔
886
        }
887

888
        return -ESRCH;
889
}
890

891
int userdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
6,421✔
892
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
893
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
6,421✔
894
        int r;
6,421✔
895

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

904
        assert(name);
6,421✔
905

906
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
6,421✔
907
                uid_t uid;
3,393✔
908

909
                if (parse_uid(name, &uid) >= 0)
3,393✔
910
                        return userdb_by_uid(uid, match, flags, ret);
3,199✔
911
        }
912

913
        if (!valid_user_group_name(name, VALID_USER_RELAX))
3,222✔
914
                return -EINVAL;
915

916
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
3,215✔
917
        if (r < 0)
3,215✔
918
                return r;
919

920
        r = query_append_uid_match(&query, match);
3,215✔
921
        if (r < 0)
3,215✔
922
                return r;
923

924
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
3,215✔
925
        if (!iterator)
3,215✔
926
                return -ENOMEM;
927

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

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

948
        if (ret)
2,430✔
949
                *ret = TAKE_PTR(ur);
2,430✔
950

951
        return 0;
952
}
953

954
static int userdb_by_uid_fallbacks(
6,927✔
955
                uid_t uid,
956
                UserDBIterator *iterator,
957
                UserDBFlags flags,
958
                UserRecord **ret) {
959
        int r;
6,927✔
960

961
        assert(uid_is_valid(uid));
6,927✔
962
        assert(iterator);
6,927✔
963
        assert(ret);
6,927✔
964

965
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
6,927✔
966
                r = dropin_user_record_by_uid(uid, NULL, flags, ret);
3,771✔
967
                if (r >= 0)
3,771✔
968
                        return r;
969
        }
970

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

981
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
6,403✔
982
                if (uid == 0)
6,188✔
983
                        return synthetic_root_user_build(ret);
1✔
984

985
                if (uid == UID_NOBODY && synthesize_nobody())
6,187✔
986
                        return synthetic_nobody_user_build(ret);
1✔
987
        }
988

989
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && uid_is_foreign(uid))
6,401✔
990
                return synthetic_foreign_user_build(uid - FOREIGN_UID_BASE, ret);
7✔
991

992
        if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
6,394✔
993
                return synthetic_numeric_user_build(uid, ret);
2,938✔
994

995
        return -ESRCH;
996
}
997

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

1003
        if (!uid_is_valid(uid))
8,011✔
1004
                return -EINVAL;
1005

1006
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(uid)));
8,011✔
1007
        if (r < 0)
8,011✔
1008
                return r;
1009

1010
        r = query_append_uid_match(&query, match);
8,011✔
1011
        if (r < 0)
8,011✔
1012
                return r;
1013

1014
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
8,011✔
1015
        if (!iterator)
8,011✔
1016
                return -ENOMEM;
1017

1018
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
8,011✔
1019
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
8,011✔
1020
        if (r >= 0) {
8,011✔
1021
                r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
7,584✔
1022
                if (r == -ENOEXEC)
7,584✔
1023
                        return r;
1024
        }
1025
        if (r < 0) {
7,581✔
1026
                r = userdb_by_uid_fallbacks(uid, iterator, flags, &ur);
6,927✔
1027
                if (r < 0)
6,927✔
1028
                        return r;
1029
        }
1030

1031
        if (!user_record_match(ur, match))
4,544✔
1032
                return -ENOEXEC;
1033

1034
        if (ret)
4,541✔
1035
                *ret = TAKE_PTR(ur);
4,541✔
1036

1037
        return 0;
1038
}
1039

1040
int userdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
41✔
1041
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1042
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
41✔
1043
        int r, qr;
41✔
1044

1045
        assert(ret);
41✔
1046

1047
        r = query_append_uid_match(&query, match);
41✔
1048
        if (r < 0)
41✔
1049
                return r;
1050

1051
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
41✔
1052
        if (!iterator)
41✔
1053
                return -ENOMEM;
1054

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

1057
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
41✔
1058
                r = userdb_iterator_block_nss_systemd(iterator);
17✔
1059
                if (r < 0)
17✔
1060
                        return r;
1061

1062
                setpwent();
17✔
1063
                iterator->nss_iterating = true;
17✔
1064
        }
1065

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

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

1079
        /* propagate IPC error, but only if there are no drop-ins */
1080
        if (qr < 0 &&
41✔
1081
            !iterator->nss_iterating &&
3✔
1082
            strv_isempty(iterator->dropins))
43✔
1083
                return qr;
1084

1085
        *ret = TAKE_PTR(iterator);
41✔
1086
        return 0;
41✔
1087
}
1088

1089
static int userdb_iterator_get_one(UserDBIterator *iterator, UserRecord **ret) {
812✔
1090
        int r;
812✔
1091

1092
        assert(iterator);
812✔
1093
        assert(iterator->what == LOOKUP_USER);
812✔
1094

1095
        if (iterator->nss_iterating) {
812✔
1096
                struct passwd *pw;
425✔
1097

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

1101
                errno = 0;
425✔
1102
                pw = getpwent();
425✔
1103
                if (pw) {
425✔
1104
                        _cleanup_free_ char *buffer = NULL;
408✔
1105
                        bool incomplete = false;
408✔
1106
                        struct spwd spwd;
408✔
1107

1108
                        if (streq_ptr(pw->pw_name, "root"))
408✔
1109
                                iterator->synthesize_root = false;
17✔
1110
                        if (pw->pw_uid == UID_NOBODY)
408✔
1111
                                iterator->synthesize_nobody = false;
17✔
1112

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

1124
                        r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
408✔
1125
                        if (r < 0)
408✔
1126
                                return r;
1127

1128
                        if (ret)
408✔
1129
                                (*ret)->incomplete = incomplete;
408✔
1130

1131
                        iterator->n_found++;
408✔
1132
                        return r;
408✔
1133
                }
1134

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

1138
                iterator->nss_iterating = false;
17✔
1139
                endpwent();
17✔
1140
        }
1141

1142
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
441✔
1143
                const char *i = iterator->dropins[iterator->current_dropin];
74✔
1144
                _cleanup_free_ char *fn = NULL;
74✔
1145
                uid_t uid;
74✔
1146
                char *e;
74✔
1147

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

1150
                r = path_extract_filename(i, &fn);
74✔
1151
                if (r < 0)
74✔
1152
                        return r;
1153

1154
                e = endswith(fn, ".user"); /* not actually a .user file? Then skip to next */
74✔
1155
                if (!e)
74✔
1156
                        continue;
×
1157

1158
                *e = 0; /* Chop off suffix */
74✔
1159

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

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

1171
                iterator->current_dropin++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
37✔
1172
                iterator->n_found++;
37✔
1173
                return 0;
37✔
1174
        }
1175

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

1180
                /* Finally, synthesize root + nobody if not done yet */
1181
                if (iterator->synthesize_root) {
50✔
1182
                        iterator->synthesize_root = false;
5✔
1183
                        iterator->n_found++;
5✔
1184
                        return synthetic_root_user_build(ret);
5✔
1185
                }
1186

1187
                if (iterator->synthesize_nobody) {
45✔
1188
                        iterator->synthesize_nobody = false;
5✔
1189
                        iterator->n_found++;
5✔
1190
                        return synthetic_nobody_user_build(ret);
5✔
1191
                }
1192

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

1198
        return r;
1199
}
1200

1201
int userdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, UserRecord **ret) {
698✔
1202
        int r;
698✔
1203

1204
        assert(iterator);
698✔
1205
        assert(iterator->what == LOOKUP_USER);
698✔
1206

1207
        for (;;) {
114✔
1208
                _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
698✔
1209

1210
                r = userdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &ur : NULL);
812✔
1211
                if (r < 0)
812✔
1212
                        return r;
1213

1214
                if (ur && !user_record_match(ur, match))
772✔
1215
                        continue;
114✔
1216

1217
                if (ret)
658✔
1218
                        *ret = TAKE_PTR(ur);
657✔
1219

1220
                return r;
1221
        }
1222
}
1223

1224
static int synthetic_root_group_build(GroupRecord **ret) {
7✔
1225
        return group_record_buildo(
7✔
1226
                        ret,
1227
                        SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
1228
                        SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(0)),
1229
                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
1230
}
1231

1232
static int synthetic_nobody_group_build(GroupRecord **ret) {
6✔
1233
        return group_record_buildo(
6✔
1234
                        ret,
1235
                        SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME)),
1236
                        SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(GID_NOBODY)),
1237
                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic")));
1238
}
1239

1240
static int synthetic_foreign_group_build(gid_t foreign_gid, GroupRecord **ret) {
7✔
1241
        assert(ret);
7✔
1242

1243
        if (!gid_is_valid(foreign_gid))
7✔
1244
                return -ESRCH;
7✔
1245
        if (foreign_gid > 0xFFFF)
7✔
1246
                return -ESRCH;
1247

1248
        _cleanup_free_ char *gn = NULL;
7✔
1249
        if (asprintf(&gn, "foreign-" GID_FMT, foreign_gid) < 0)
7✔
1250
                return -ENOMEM;
1251

1252
        _cleanup_free_ char *d = NULL;
7✔
1253
        if (asprintf(&d, "Foreign System Image GID " GID_FMT, foreign_gid) < 0)
7✔
1254
                return -ENOMEM;
1255

1256
        return group_record_buildo(
7✔
1257
                        ret,
1258
                        SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(gn)),
1259
                        SD_JSON_BUILD_PAIR("description", SD_JSON_BUILD_STRING(d)),
1260
                        SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_gid)),
1261
                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign")));
1262
}
1263

1264
static int synthetic_numeric_group_build(gid_t gid, GroupRecord **ret) {
2,841✔
1265
        assert(ret);
2,841✔
1266

1267
        if (gid == 0) /* This should be handled by synthetic_root_group_build() */
2,841✔
1268
                return -ESRCH;
2,841✔
1269

1270
        if (!gid_is_system(gid))
2,840✔
1271
                return -ESRCH;
1272

1273
        _cleanup_free_ char *gn = NULL;
2,835✔
1274
        if (asprintf(&gn, "unknown-" GID_FMT, gid) < 0)
2,835✔
1275
                return -ENOMEM;
1276

1277
        _cleanup_free_ char *d = NULL;
2,835✔
1278
        if (asprintf(&d, "Unknown System GID " UID_FMT, gid) < 0)
2,835✔
1279
                return -ENOMEM;
1280

1281
        return group_record_buildo(
2,835✔
1282
                        ret,
1283
                        SD_JSON_BUILD_PAIR_STRING("groupName", gn),
1284
                        SD_JSON_BUILD_PAIR_STRING("description", d),
1285
                        SD_JSON_BUILD_PAIR_UNSIGNED("gid", gid),
1286
                        SD_JSON_BUILD_PAIR_STRING("disposition", "system"));
1287
}
1288

1289
static int query_append_gid_match(sd_json_variant **query, const UserDBMatch *match) {
36,414✔
1290
        int r;
36,414✔
1291

1292
        assert(query);
36,414✔
1293

1294
        if (!userdb_match_is_set(match))
36,414✔
1295
                return 0;
1296

1297
        r = sd_json_variant_merge_objectbo(
9,545✔
1298
                        query,
1299
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
1300
                        SD_JSON_BUILD_PAIR_CONDITION(match->gid_min > 0, "gidMin", SD_JSON_BUILD_UNSIGNED(match->gid_min)),
1301
                        SD_JSON_BUILD_PAIR_CONDITION(match->gid_max < GID_INVALID-1, "gidMax", SD_JSON_BUILD_UNSIGNED(match->gid_max)));
1302
        if (r < 0)
9,543✔
1303
                return r;
1304

1305
        return query_append_disposition_mask(query, match->disposition_mask);
9,543✔
1306
}
1307

1308
static int groupdb_by_name_fallbacks(
24,992✔
1309
                const char *name,
1310
                UserDBIterator *iterator,
1311
                UserDBFlags flags,
1312
                GroupRecord **ret) {
1313

1314
        int r;
24,992✔
1315

1316
        assert(name);
24,992✔
1317
        assert(iterator);
24,992✔
1318
        assert(ret);
24,992✔
1319

1320
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
24,992✔
1321
                r = dropin_group_record_by_name(name, NULL, flags, ret);
12,407✔
1322
                if (r >= 0)
12,407✔
1323
                        return r;
1324
        }
1325

1326
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
24,967✔
1327
                r = userdb_iterator_block_nss_systemd(iterator);
1,875✔
1328
                if (r >= 0) {
1,875✔
1329
                        r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
1,875✔
1330
                        if (r >= 0)
1,875✔
1331
                                return r;
1332
                }
1333
        }
1334

1335
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
23,197✔
1336
                if (streq(name, "root"))
12,688✔
1337
                        return synthetic_root_group_build(ret);
1✔
1338

1339
                if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
12,687✔
1340
                        return synthetic_nobody_group_build(ret);
×
1341
        }
1342

1343
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
23,196✔
1344
                uid_t foreign_gid;
12,687✔
1345
                r = user_name_foreign_extract_uid(name, &foreign_gid); /* Same for UID + GID */
12,687✔
1346
                if (r < 0)
12,687✔
1347
                        return r;
×
1348
                if (r > 0)
12,687✔
1349
                        return synthetic_foreign_group_build(foreign_gid, ret);
×
1350
        }
1351

1352
        return -ESRCH;
1353
}
1354

1355
int groupdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
29,805✔
1356
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1357
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
29,805✔
1358
        int r;
29,805✔
1359

1360
        assert(name);
29,805✔
1361

1362
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
29,805✔
1363
                gid_t gid;
4,809✔
1364

1365
                if (parse_gid(name, &gid) >= 0)
4,809✔
1366
                        return groupdb_by_gid(gid, match, flags, ret);
3,016✔
1367
        }
1368

1369
        if (!valid_user_group_name(name, VALID_USER_RELAX))
26,789✔
1370
                return -EINVAL;
1371

1372
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
26,784✔
1373
        if (r < 0)
26,784✔
1374
                return r;
1375

1376
        r = query_append_gid_match(&query, match);
26,784✔
1377
        if (r < 0)
26,784✔
1378
                return r;
1379

1380
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
26,784✔
1381
        if (!iterator)
26,784✔
1382
                return -ENOMEM;
1383

1384
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
26,784✔
1385
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
26,784✔
1386
        if (r >= 0) {
26,784✔
1387
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
16,271✔
1388
                if (r == -ENOEXEC)
16,271✔
1389
                        return r;
1390
        }
1391
        if (r < 0) {
16,268✔
1392
                r = groupdb_by_name_fallbacks(name, iterator, flags, &gr);
24,992✔
1393
                if (r < 0)
24,992✔
1394
                        return r;
1395
        }
1396

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

1401
        if (ret)
3,583✔
1402
                *ret = TAKE_PTR(gr);
3,583✔
1403

1404
        return 0;
1405
}
1406

1407
static int groupdb_by_gid_fallbacks(
9,135✔
1408
                gid_t gid,
1409
                UserDBIterator *iterator,
1410
                UserDBFlags flags,
1411
                GroupRecord **ret) {
1412
        int r;
9,135✔
1413

1414
        assert(gid_is_valid(gid));
9,135✔
1415
        assert(iterator);
9,135✔
1416
        assert(ret);
9,135✔
1417

1418
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
9,135✔
1419
                r = dropin_group_record_by_gid(gid, NULL, flags, ret);
4,081✔
1420
                if (r >= 0)
4,081✔
1421
                        return r;
1422
        }
1423

1424
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
9,095✔
1425
                r = userdb_iterator_block_nss_systemd(iterator);
3,132✔
1426
                if (r >= 0) {
3,132✔
1427
                        r = nss_group_record_by_gid(gid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
3,132✔
1428
                        if (r >= 0)
3,132✔
1429
                                return r;
1430
                }
1431
        }
1432

1433
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
8,908✔
1434
                if (gid == 0)
7,995✔
1435
                        return synthetic_root_group_build(ret);
1✔
1436

1437
                if (gid == GID_NOBODY && synthesize_nobody())
7,994✔
1438
                        return synthetic_nobody_group_build(ret);
1✔
1439
        }
1440

1441
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && gid_is_foreign(gid))
8,906✔
1442
                return synthetic_foreign_group_build(gid - FOREIGN_UID_BASE, ret);
7✔
1443

1444
        if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
8,899✔
1445
                return synthetic_numeric_group_build(gid, ret);
2,841✔
1446

1447
        return -ESRCH;
1448
}
1449

1450
int groupdb_by_gid(gid_t gid, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
9,612✔
1451
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1452
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
9,612✔
1453
        int r;
9,612✔
1454

1455
        if (!gid_is_valid(gid))
9,612✔
1456
                return -EINVAL;
1457

1458
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(gid)));
9,612✔
1459
        if (r < 0)
9,612✔
1460
                return r;
1461

1462
        r = query_append_gid_match(&query, match);
9,612✔
1463
        if (r < 0)
9,612✔
1464
                return r;
1465

1466
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
9,612✔
1467
        if (!iterator)
9,612✔
1468
                return -ENOMEM;
1469

1470
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
9,612✔
1471
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
9,612✔
1472
        if (r >= 0) {
9,612✔
1473
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
8,664✔
1474
                if (r == -ENOEXEC)
8,664✔
1475
                        return r;
1476
        }
1477
        if (r < 0) {
8,661✔
1478
                r = groupdb_by_gid_fallbacks(gid, iterator, flags, &gr);
9,135✔
1479
                if (r < 0)
9,135✔
1480
                        return r;
1481
        }
1482

1483
        if (!group_record_match(gr, match))
3,545✔
1484
                return -ENOEXEC;
1485

1486
        if (ret)
3,543✔
1487
                *ret = TAKE_PTR(gr);
3,543✔
1488

1489
        return 0;
1490
}
1491

1492
int groupdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
18✔
1493
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1494
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
18✔
1495
        int r, qr;
18✔
1496

1497
        assert(ret);
18✔
1498

1499
        r = query_append_gid_match(&query, match);
18✔
1500
        if (r < 0)
18✔
1501
                return r;
1502

1503
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
18✔
1504
        if (!iterator)
18✔
1505
                return -ENOMEM;
1506

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

1509
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
18✔
1510
                r = userdb_iterator_block_nss_systemd(iterator);
8✔
1511
                if (r < 0)
8✔
1512
                        return r;
1513

1514
                setgrent();
8✔
1515
                iterator->nss_iterating = true;
8✔
1516
        }
1517

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

1529
        if (qr < 0 &&
18✔
1530
            !iterator->nss_iterating &&
1✔
1531
            strv_isempty(iterator->dropins))
19✔
1532
                return qr;
1533

1534
        *ret = TAKE_PTR(iterator);
18✔
1535
        return 0;
18✔
1536
}
1537

1538
static int groupdb_iterator_get_one(UserDBIterator *iterator, GroupRecord **ret) {
726✔
1539
        int r;
726✔
1540

1541
        assert(iterator);
726✔
1542
        assert(iterator->what == LOOKUP_GROUP);
726✔
1543

1544
        if (iterator->nss_iterating) {
726✔
1545
                struct group *gr;
456✔
1546

1547
                errno = 0;
456✔
1548
                gr = getgrent();
456✔
1549
                if (gr) {
456✔
1550
                        _cleanup_free_ char *buffer = NULL;
448✔
1551
                        bool incomplete = false;
448✔
1552
                        struct sgrp sgrp;
448✔
1553

1554
                        if (streq_ptr(gr->gr_name, "root"))
448✔
1555
                                iterator->synthesize_root = false;
8✔
1556
                        if (gr->gr_gid == GID_NOBODY)
448✔
1557
                                iterator->synthesize_nobody = false;
8✔
1558

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

1570
                        r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
448✔
1571
                        if (r < 0)
448✔
1572
                                return r;
1573

1574
                        if (ret)
448✔
1575
                                (*ret)->incomplete = incomplete;
448✔
1576

1577
                        iterator->n_found++;
448✔
1578
                        return r;
448✔
1579
                }
1580

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

1584
                iterator->nss_iterating = false;
8✔
1585
                endgrent();
8✔
1586
        }
1587

1588
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
296✔
1589
                const char *i = iterator->dropins[iterator->current_dropin];
36✔
1590
                _cleanup_free_ char *fn = NULL;
36✔
1591
                gid_t gid;
36✔
1592
                char *e;
36✔
1593

1594
                r = path_extract_filename(i, &fn);
36✔
1595
                if (r < 0)
36✔
1596
                        return r;
1597

1598
                e = endswith(fn, ".group");
36✔
1599
                if (!e)
36✔
1600
                        continue;
×
1601

1602
                *e = 0; /* Chop off suffix */
36✔
1603

1604
                if (parse_gid(fn, &gid) < 0)
36✔
1605
                        continue;
18✔
1606

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

1613
                iterator->current_dropin++;
18✔
1614
                iterator->n_found++;
18✔
1615
                return 0;
18✔
1616
        }
1617

1618
        r = userdb_process(iterator, NULL, ret, NULL, NULL);
260✔
1619
        if (r < 0) {
260✔
1620
                if (iterator->synthesize_root) {
28✔
1621
                        iterator->synthesize_root = false;
5✔
1622
                        iterator->n_found++;
5✔
1623
                        return synthetic_root_group_build(ret);
5✔
1624
                }
1625

1626
                if (iterator->synthesize_nobody) {
23✔
1627
                        iterator->synthesize_nobody = false;
5✔
1628
                        iterator->n_found++;
5✔
1629
                        return synthetic_nobody_group_build(ret);
5✔
1630
                }
1631

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

1637
        return r;
1638
}
1639

1640
int groupdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, GroupRecord **ret) {
484✔
1641
        int r;
484✔
1642

1643
        assert(iterator);
484✔
1644
        assert(iterator->what == LOOKUP_GROUP);
484✔
1645

1646
        for (;;) {
242✔
1647
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
484✔
1648

1649
                r = groupdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &gr : NULL);
726✔
1650
                if (r < 0)
726✔
1651
                        return r;
1652

1653
                if (gr && !group_record_match(gr, match))
708✔
1654
                        continue;
242✔
1655

1656
                if (ret)
466✔
1657
                        *ret = TAKE_PTR(gr);
466✔
1658

1659
                return r;
1660
        }
1661
}
1662

1663
static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
12,623✔
1664
        int r;
12,623✔
1665

1666
        r = conf_files_list_nulstr(
12,623✔
1667
                        &i->dropins,
1668
                        ".membership",
1669
                        NULL,
1670
                        CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED,
1671
                        USERDB_DROPIN_DIR_NULSTR("userdb"));
1672
        if (r < 0)
12,623✔
1673
                log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
×
1674
}
12,623✔
1675

1676
int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1,463✔
1677
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1678
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
1,463✔
1679
        int r, qr;
1,463✔
1680

1681
        assert(ret);
1,463✔
1682

1683
        if (!valid_user_group_name(name, VALID_USER_RELAX))
1,463✔
1684
                return -EINVAL;
1685

1686
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
1,461✔
1687
        if (r < 0)
1,461✔
1688
                return r;
1689

1690
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1,461✔
1691
        if (!iterator)
1,461✔
1692
                return -ENOMEM;
1693

1694
        iterator->filter_user_name = strdup(name);
1,461✔
1695
        if (!iterator->filter_user_name)
1,461✔
1696
                return -ENOMEM;
1697

1698
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1,461✔
1699

1700
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1,461✔
1701
                r = userdb_iterator_block_nss_systemd(iterator);
120✔
1702
                if (r < 0)
120✔
1703
                        return r;
1704

1705
                setgrent();
120✔
1706
                iterator->nss_iterating = true;
120✔
1707
        }
1708

1709
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1,461✔
1710
                discover_membership_dropins(iterator, flags);
1,285✔
1711

1712
        if (qr < 0 &&
1,461✔
1713
            !iterator->nss_iterating &&
1,165✔
1714
            strv_isempty(iterator->dropins))
2,628✔
1715
                return qr;
1716

1717
        *ret = TAKE_PTR(iterator);
296✔
1718
        return 0;
296✔
1719
}
1720

1721
int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
25,921✔
1722
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1723
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
25,921✔
1724
        int r, qr;
25,921✔
1725

1726
        assert(ret);
25,921✔
1727

1728
        if (!valid_user_group_name(name, VALID_USER_RELAX))
25,921✔
1729
                return -EINVAL;
1730

1731
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
25,919✔
1732
        if (r < 0)
25,919✔
1733
                return r;
1734

1735
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
25,919✔
1736
        if (!iterator)
25,919✔
1737
                return -ENOMEM;
1738

1739
        iterator->filter_group_name = strdup(name);
25,919✔
1740
        if (!iterator->filter_group_name)
25,919✔
1741
                return -ENOMEM;
1742

1743
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
25,919✔
1744

1745
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
25,919✔
1746
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
22✔
1747

1748
                r = userdb_iterator_block_nss_systemd(iterator);
22✔
1749
                if (r < 0)
22✔
1750
                        return r;
1751

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

1759
                        iterator->index_members_of_group = 0;
10✔
1760

1761
                        iterator->found_group_name = strdup(name);
10✔
1762
                        if (!iterator->found_group_name)
10✔
1763
                                return -ENOMEM;
1764
                }
1765
        }
1766

1767
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
25,919✔
1768
                discover_membership_dropins(iterator, flags);
11,335✔
1769

1770
        if (qr < 0 &&
25,919✔
1771
            strv_isempty(iterator->members_of_group) &&
11,313✔
1772
            strv_isempty(iterator->dropins))
37,234✔
1773
                return qr;
1774

1775
        *ret = TAKE_PTR(iterator);
14,606✔
1776
        return 0;
14,606✔
1777
}
1778

1779
int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
5✔
1780
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1781
        int r, qr;
5✔
1782

1783
        assert(ret);
5✔
1784

1785
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
5✔
1786
        if (!iterator)
5✔
1787
                return -ENOMEM;
1788

1789
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, NULL, flags);
5✔
1790

1791
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
5✔
1792
                r = userdb_iterator_block_nss_systemd(iterator);
3✔
1793
                if (r < 0)
3✔
1794
                        return r;
1795

1796
                setgrent();
3✔
1797
                iterator->nss_iterating = true;
3✔
1798
        }
1799

1800
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
5✔
1801
                discover_membership_dropins(iterator, flags);
3✔
1802

1803
        if (qr < 0 &&
5✔
1804
            !iterator->nss_iterating &&
×
1805
            strv_isempty(iterator->dropins))
5✔
1806
                return qr;
1807

1808
        *ret = TAKE_PTR(iterator);
5✔
1809
        return 0;
5✔
1810
}
1811

1812
int membershipdb_iterator_get(
14,980✔
1813
                UserDBIterator *iterator,
1814
                char **ret_user,
1815
                char **ret_group) {
1816

1817
        int r;
14,980✔
1818

1819
        assert(iterator);
14,980✔
1820

1821
        for (;;) {
15,060✔
1822
                /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1823
                if (!iterator->members_of_group) {
15,020✔
1824
                        struct group *g;
14,972✔
1825

1826
                        if (!iterator->nss_iterating)
14,972✔
1827
                                break;
1828

1829
                        assert(!iterator->found_user_name);
153✔
1830
                        do {
7,023✔
1831
                                errno = 0;
7,023✔
1832
                                g = getgrent();
7,023✔
1833
                                if (!g) {
7,023✔
1834
                                        if (errno != 0)
123✔
1835
                                                log_debug_errno(errno, "Failure during NSS group iteration, ignoring: %m");
×
1836
                                        break;
1837
                                }
1838

1839
                        } while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
7,070✔
1840
                                                              strv_isempty(g->gr_mem));
340✔
1841

1842
                        if (g) {
153✔
1843
                                r = free_and_strdup(&iterator->found_group_name, g->gr_name);
30✔
1844
                                if (r < 0)
30✔
1845
                                        return r;
1846

1847
                                if (iterator->filter_user_name)
30✔
1848
                                        iterator->members_of_group = strv_new(iterator->filter_user_name);
15✔
1849
                                else
1850
                                        iterator->members_of_group = strv_copy(g->gr_mem);
15✔
1851
                                if (!iterator->members_of_group)
30✔
1852
                                        return -ENOMEM;
1853

1854
                                iterator->index_members_of_group = 0;
30✔
1855
                        } else {
1856
                                iterator->nss_iterating = false;
123✔
1857
                                endgrent();
123✔
1858
                                break;
123✔
1859
                        }
1860
                }
1861

1862
                assert(iterator->found_group_name);
78✔
1863
                assert(iterator->members_of_group);
78✔
1864
                assert(!iterator->found_user_name);
78✔
1865

1866
                if (iterator->members_of_group[iterator->index_members_of_group]) {
78✔
1867
                        _cleanup_free_ char *cu = NULL, *cg = NULL;
38✔
1868

1869
                        if (ret_user) {
38✔
1870
                                cu = strdup(iterator->members_of_group[iterator->index_members_of_group]);
38✔
1871
                                if (!cu)
38✔
1872
                                        return -ENOMEM;
1873
                        }
1874

1875
                        if (ret_group) {
38✔
1876
                                cg = strdup(iterator->found_group_name);
38✔
1877
                                if (!cg)
38✔
1878
                                        return -ENOMEM;
1879
                        }
1880

1881
                        if (ret_user)
38✔
1882
                                *ret_user = TAKE_PTR(cu);
38✔
1883

1884
                        if (ret_group)
38✔
1885
                                *ret_group = TAKE_PTR(cg);
38✔
1886

1887
                        iterator->index_members_of_group++;
38✔
1888
                        return 0;
38✔
1889
                }
1890

1891
                iterator->members_of_group = strv_free(iterator->members_of_group);
40✔
1892
                iterator->found_group_name = mfree(iterator->found_group_name);
40✔
1893
        }
1894

1895
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
14,942✔
1896
                const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
×
1897
                _cleanup_free_ char *un = NULL, *gn = NULL;
×
1898

1899
                e = endswith(i, ".membership");
×
1900
                if (!e)
×
1901
                        continue;
×
1902

1903
                c = memchr(i, ':', e - i);
×
1904
                if (!c)
×
1905
                        continue;
×
1906

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

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

1926
                iterator->current_dropin++;
×
1927
                iterator->n_found++;
×
1928

1929
                if (ret_user)
×
1930
                        *ret_user = TAKE_PTR(un);
×
1931
                if (ret_group)
×
1932
                        *ret_group = TAKE_PTR(gn);
×
1933

1934
                return 0;
1935
        }
1936

1937
        r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
14,942✔
1938
        if (r < 0 && iterator->n_found > 0)
14,942✔
1939
                return -ESRCH;
25✔
1940

1941
        return r;
1942
}
1943

1944
int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret) {
14,559✔
1945
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
14,559✔
1946
        _cleanup_strv_free_ char **members = NULL;
14,559✔
1947
        int r;
14,559✔
1948

1949
        assert(name);
14,559✔
1950
        assert(ret);
14,559✔
1951

1952
        r = membershipdb_by_group(name, flags, &iterator);
14,559✔
1953
        if (r < 0)
14,559✔
1954
                return r;
1955

1956
        for (;;) {
14,558✔
1957
                _cleanup_free_ char *user_name = NULL;
×
1958

1959
                r = membershipdb_iterator_get(iterator, &user_name, NULL);
14,558✔
1960
                if (r == -ESRCH)
14,558✔
1961
                        break;
1962
                if (r < 0)
×
1963
                        return r;
1964

1965
                r = strv_consume(&members, TAKE_PTR(user_name));
×
1966
                if (r < 0)
×
1967
                        return r;
1968
        }
1969

1970
        strv_sort_uniq(members);
14,558✔
1971

1972
        *ret = TAKE_PTR(members);
14,558✔
1973
        return 0;
14,558✔
1974
}
1975

1976
int userdb_block_nss_systemd(int b) {
18,093✔
1977
        _cleanup_(dlclosep) void *dl = NULL;
18,093✔
1978
        int (*call)(bool b);
18,093✔
1979

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

1982
        dl = dlopen(LIBDIR "/libnss_systemd.so.2", RTLD_NOW|RTLD_NODELETE);
18,093✔
1983
        if (!dl) {
18,093✔
1984
                /* If the file isn't installed, don't complain loudly */
1985
                log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
×
1986
                return 0;
×
1987
        }
1988

1989
        log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2");
18,093✔
1990

1991
        call = dlsym(dl, "_nss_systemd_block");
18,093✔
1992
        if (!call)
18,093✔
1993
                /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1994
                return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
×
1995
                                       "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());
1996

1997
        return call(b);
18,093✔
1998
}
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