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

systemd / systemd / 15243357723

25 May 2025 07:40AM UTC coverage: 72.048% (-0.005%) from 72.053%
15243357723

push

github

DaanDeMeyer
basic: Move LogRatelimit struct to log-ratelimit.h as well

299140 of 415197 relevant lines covered (72.05%)

701272.84 hits per line

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

87.98
/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,476✔
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) {
76,135✔
72
        if (!iterator)
76,135✔
73
                return NULL;
74

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

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

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

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

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

88
                break;
89

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

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

96
                break;
97

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

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

108
                break;
109

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

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

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

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

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

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

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

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

139
        return i;
75,982✔
140
}
141

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

145
        assert(iterator);
8,754✔
146

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

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

154
        iterator->nss_systemd_blocked = true;
8,754✔
155
        return 1;
8,754✔
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) {
4,936✔
164
        sd_json_variant_unref(d->record);
4,936✔
165
}
4,936✔
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,394✔
178
                UserDBIterator *iterator,
179
                sd_varlink *link,
180
                sd_json_variant *parameters,
181
                const char *error_id) {
182

183
        int r;
147,394✔
184

185
        assert(iterator);
147,394✔
186
        assert(link);
147,394✔
187
        assert(error_id);
147,394✔
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;
990,320✔
205
        STRV_FOREACH(f, fields) {
990,320✔
206
                if (!sd_varlink_error_is_invalid_parameter(error_id, parameters, *f))
865,642✔
207
                        continue;
842,926✔
208

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

212
                restart = true;
213
                break;
214
        }
215

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

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

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

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

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

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

254
                r = userdb_maybe_restart_query(iterator, link, parameters, error_id);
147,394✔
255
                if (r < 0)
147,394✔
256
                        return r;
257
                if (r > 0) {
147,394✔
258
                        r = 0;
22,716✔
259
                        goto finish;
22,716✔
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,678✔
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,636✔
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,678✔
284
        }
285

286
        switch (iterator->what) {
4,971✔
287

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

394
                r = 0;
2,219✔
395
                goto finish;
2,219✔
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,438✔
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)
151,832✔
433
                iterator->error = -r;
146,106✔
434

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

440
static int userdb_connect(
153,316✔
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,316✔
448
        int r;
153,316✔
449

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

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

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

460
        if (!iterator->event) {
153,308✔
461
                r = sd_event_new(&iterator->event);
49,159✔
462
                if (r < 0)
49,159✔
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,308✔
467
        if (r < 0)
153,308✔
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,308✔
474
        if (r < 0)
153,308✔
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,308✔
478
        if (r < 0)
153,308✔
479
                return log_debug_errno(r, "Failed to bind reply callback: %m");
×
480

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

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

492
        if (more)
153,308✔
493
                r = sd_varlink_observe(vl, method, patched_query);
45,536✔
494
        else
495
                r = sd_varlink_invoke(vl, method, patched_query);
107,772✔
496
        if (r < 0)
153,308✔
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,308✔
500
        if (r < 0)
153,308✔
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,982✔
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,982✔
513
        _cleanup_closedir_ DIR *d = NULL;
75,982✔
514
        const char *e;
75,982✔
515
        int r, ret = 0;
75,982✔
516

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

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

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

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

540
        e = getenv("SYSTEMD_ONLY_USERDB");
49,167✔
541
        if (e) {
49,167✔
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,836✔
549
            !strv_contains(except, "io.systemd.Multiplexer") &&
9,669✔
550
            (!only || strv_contains(only, "io.systemd.Multiplexer"))) {
9,669✔
551
                r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, query);
9,669✔
552
                if (r >= 0) {
9,669✔
553
                        iterator->nss_covered = true; /* The multiplexer does NSS */
9,669✔
554
                        iterator->dropin_covered = true; /* It also handles drop-in stuff */
9,669✔
555
                        return 0;
9,669✔
556
                }
557
        }
558

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

564
                return -errno;
×
565
        }
566

567
        FOREACH_DIRENT(de, d, return -errno) {
327,638✔
568
                _cleanup_free_ char *p = NULL;
120,931✔
569
                bool is_nss, is_dropin;
209,144✔
570

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

574
                if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
169,654✔
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,648✔
583
                if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
169,648✔
584
                        continue;
39,458✔
585

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

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

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

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

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

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

612
        if (set_isempty(iterator->links))
39,498✔
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,766✔
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,766✔
627

628
        assert(iterator);
49,766✔
629

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

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

644
                        return 0;
2,492✔
645
                }
646

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

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

660
                        return 0;
2,444✔
661
                }
662

663
                if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
778,557✔
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)) {
778,522✔
683
                        if (iterator->error == 0)
44,795✔
684
                                return -ESRCH;
685

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

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

692
                r = sd_event_run(iterator->event, UINT64_MAX);
733,727✔
693
                if (r < 0)
733,727✔
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) {
3✔
720
        assert(ret);
3✔
721

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

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

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

735
        return user_record_buildo(
2✔
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,962✔
772
        int r;
12,962✔
773

774
        assert(name);
12,962✔
775
        assert(ret_uid);
12,962✔
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,962✔
781
        if (!e)
12,962✔
782
                goto nomatch;
12,960✔
783

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

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

792
        *ret_uid = uid;
×
793
        return 1;
×
794

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

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

803
        assert(query);
15,717✔
804

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

808
        _cleanup_strv_free_ char **dispositions = NULL;
15,709✔
809
        for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
125,672✔
810
                if (!BITS_SET(mask, d))
219,926✔
811
                        continue;
78,554✔
812

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

818
        return sd_json_variant_merge_objectbo(
15,709✔
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,314✔
824
        int r;
11,314✔
825

826
        assert(query);
11,314✔
827

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

831
        r = sd_json_variant_merge_objectbo(
6,245✔
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,243✔
837
                return r;
838

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

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

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

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

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

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

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

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

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

888
        return -ESRCH;
889
}
890

891
int userdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
6,476✔
892
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
893
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
6,476✔
894
        int r;
6,476✔
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,476✔
905

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

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

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

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

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

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

928
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
3,272✔
929
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
3,272✔
930
        if (r >= 0) {
3,272✔
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,157✔
938
                if (r < 0)
2,157✔
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,512✔
946
                return -ENOEXEC;
947

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

951
        return 0;
952
}
953

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

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

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

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

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

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

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

992
        if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
6,385✔
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,001✔
999
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1000
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
8,001✔
1001
        int r;
8,001✔
1002

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

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

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

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

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

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

1034
        if (ret)
4,540✔
1035
                *ret = TAKE_PTR(ur);
4,540✔
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) {
2✔
1241
        assert(ret);
2✔
1242

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

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

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

1256
        return group_record_buildo(
2✔
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,789✔
1290
        int r;
36,789✔
1291

1292
        assert(query);
36,789✔
1293

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

1297
        r = sd_json_variant_merge_objectbo(
9,476✔
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,474✔
1303
                return r;
1304

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

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

1314
        int r;
25,432✔
1315

1316
        assert(name);
25,432✔
1317
        assert(iterator);
25,432✔
1318
        assert(ret);
25,432✔
1319

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

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

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

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

1343
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
23,609✔
1344
                uid_t foreign_gid;
12,686✔
1345
                r = user_name_foreign_extract_uid(name, &foreign_gid); /* Same for UID + GID */
12,686✔
1346
                if (r < 0)
12,686✔
1347
                        return r;
×
1348
                if (r > 0)
12,686✔
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) {
30,197✔
1356
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1357
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
30,197✔
1358
        int r;
30,197✔
1359

1360
        assert(name);
30,197✔
1361

1362
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
30,197✔
1363
                gid_t gid;
4,761✔
1364

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

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

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

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

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

1384
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
27,176✔
1385
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
27,176✔
1386
        if (r >= 0) {
27,176✔
1387
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
16,249✔
1388
                if (r == -ENOEXEC)
16,249✔
1389
                        return r;
1390
        }
1391
        if (r < 0) {
16,246✔
1392
                r = groupdb_by_name_fallbacks(name, iterator, flags, &gr);
25,432✔
1393
                if (r < 0)
25,432✔
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,564✔
1399
                return -ENOEXEC;
1400

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

1404
        return 0;
1405
}
1406

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

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

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

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

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

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

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

1444
        if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
8,925✔
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,595✔
1451
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1452
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
9,595✔
1453
        int r;
9,595✔
1454

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

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

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

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

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

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

1486
        if (ret)
3,500✔
1487
                *ret = TAKE_PTR(gr);
3,500✔
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) {
13,115✔
1664
        int r;
13,115✔
1665

1666
        r = conf_files_list_nulstr(
13,115✔
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)
13,115✔
1673
                log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
×
1674
}
13,115✔
1675

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

1681
        assert(ret);
1,570✔
1682

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

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

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

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

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

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

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

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

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

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

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

1726
        assert(ret);
26,309✔
1727

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

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

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

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

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

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

1748
                r = userdb_iterator_block_nss_systemd(iterator);
20✔
1749
                if (r < 0)
20✔
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);
20✔
1754
                if (gr) {
20✔
1755
                        iterator->members_of_group = strv_copy(gr->members);
8✔
1756
                        if (!iterator->members_of_group)
8✔
1757
                                return -ENOMEM;
1758

1759
                        iterator->index_members_of_group = 0;
8✔
1760

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

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

1770
        if (qr < 0 &&
26,307✔
1771
            strv_isempty(iterator->members_of_group) &&
11,701✔
1772
            strv_isempty(iterator->dropins))
38,010✔
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) {
4✔
1780
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1781
        int r, qr;
4✔
1782

1783
        assert(ret);
4✔
1784

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

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

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

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

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

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

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

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

1817
        int r;
14,972✔
1818

1819
        assert(iterator);
14,972✔
1820

1821
        for (;;) {
15,034✔
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,003✔
1824
                        struct group *g;
14,965✔
1825

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

1829
                        assert(!iterator->found_user_name);
146✔
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,012✔
1840
                                                              strv_isempty(g->gr_mem));
224✔
1841

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

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

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

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

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

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

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

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

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

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

1891
                iterator->members_of_group = strv_free(iterator->members_of_group);
31✔
1892
                iterator->found_group_name = mfree(iterator->found_group_name);
31✔
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,561✔
1945
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
14,561✔
1946
        _cleanup_strv_free_ char **members = NULL;
14,561✔
1947
        int r;
14,561✔
1948

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

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

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

1959
                r = membershipdb_iterator_get(iterator, &user_name, NULL);
14,560✔
1960
                if (r == -ESRCH)
14,560✔
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,560✔
1971

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

1976
int userdb_block_nss_systemd(int b) {
18,087✔
1977
        _cleanup_(dlclosep) void *dl = NULL;
18,087✔
1978
        int (*call)(bool b);
18,087✔
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,087✔
1983
        if (!dl) {
18,087✔
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,087✔
1990

1991
        call = dlsym(dl, "_nss_systemd_block");
18,087✔
1992
        if (!call)
18,087✔
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,087✔
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