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

systemd / systemd / 15357952084

30 May 2025 07:44PM UTC coverage: 72.024% (-0.04%) from 72.064%
15357952084

push

github

yuwata
terminal-util: fix typo

Follow-up for 5321b957b.

299538 of 415888 relevant lines covered (72.02%)

703250.55 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,492✔
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,264✔
72
        if (!iterator)
75,264✔
73
                return NULL;
74

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

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

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

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

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

88
                break;
89

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

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

96
                break;
97

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

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

108
                break;
109

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

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

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

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

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

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

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

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

139
        return i;
75,098✔
140
}
141

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

145
        assert(iterator);
8,685✔
146

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

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

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

183
        int r;
146,839✔
184

185
        assert(iterator);
146,839✔
186
        assert(link);
146,839✔
187
        assert(error_id);
146,839✔
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;
986,687✔
205
        STRV_FOREACH(f, fields) {
986,687✔
206
                if (!sd_varlink_error_is_invalid_parameter(error_id, parameters, *f))
862,310✔
207
                        continue;
839,848✔
208

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

212
                restart = true;
213
                break;
214
        }
215

216
        if (!restart)
146,839✔
217
                return 0;
146,839✔
218

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

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

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

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

251
        if (error_id) {
151,827✔
252
                log_debug("Got lookup error: %s", error_id);
146,839✔
253

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

394
                r = 0;
2,209✔
395
                goto finish;
2,209✔
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,453✔
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,292✔
433
                iterator->error = -r;
145,275✔
434

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

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

450
        assert(iterator);
152,792✔
451
        assert(path);
152,792✔
452
        assert(method);
152,792✔
453

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

458
        sd_varlink_set_userdata(vl, iterator);
152,784✔
459

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

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

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

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

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

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

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

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

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

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

564
                return -errno;
×
565
        }
566

567
        FOREACH_DIRENT(de, d, return -errno) {
326,649✔
568
                _cleanup_free_ char *p = NULL;
120,674✔
569
                bool is_nss, is_dropin;
208,563✔
570

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

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

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

591
                if (strv_contains(except, de->d_name))
120,794✔
592
                        continue;
120✔
593

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

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

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

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

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

628
        assert(iterator);
49,621✔
629

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

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

644
                        return 0;
2,519✔
645
                }
646

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

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

660
                        return 0;
2,434✔
661
                }
662

663
                if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
775,057✔
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)) {
775,022✔
683
                        if (iterator->error == 0)
44,633✔
684
                                return -ESRCH;
685

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

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

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

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

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

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

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

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

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

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

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

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

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

803
        assert(query);
15,604✔
804

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

808
        _cleanup_strv_free_ char **dispositions = NULL;
15,596✔
809
        for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
124,768✔
810
                if (!BITS_SET(mask, d))
218,344✔
811
                        continue;
77,990✔
812

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

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

826
        assert(query);
11,402✔
827

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

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

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

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

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

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

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

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

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

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

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

888
        return -ESRCH;
889
}
890

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

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

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

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

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

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

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

928
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
3,303✔
929
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
3,303✔
930
        if (r >= 0) {
3,303✔
931
                r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
1,740✔
932
                if (r == -ENOEXEC) /* found a user matching UID or name, but not filter. In this case the
1,740✔
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,737✔
937
                r = userdb_by_name_fallbacks(name, iterator, flags, &ur);
2,182✔
938
                if (r < 0)
2,182✔
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,515✔
946
                return -ENOEXEC;
947

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

951
        return 0;
952
}
953

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1037
        return 0;
1038
}
1039

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

1045
        assert(ret);
43✔
1046

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

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

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

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

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

1066
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
43✔
1067
                r = conf_files_list_nulstr(
42✔
1068
                                &iterator->dropins,
21✔
1069
                                ".user",
1070
                                NULL,
1071
                                CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1072
                                USERDB_DROPIN_DIR_NULSTR("userdb"));
1073
                if (r < 0)
21✔
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 &&
43✔
1081
            !iterator->nss_iterating &&
4✔
1082
            strv_isempty(iterator->dropins))
46✔
1083
                return qr;
1084

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

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

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

1095
        if (iterator->nss_iterating) {
844✔
1096
                struct passwd *pw;
450✔
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;
450✔
1102
                pw = getpwent();
450✔
1103
                if (pw) {
450✔
1104
                        _cleanup_free_ char *buffer = NULL;
432✔
1105
                        bool incomplete = false;
432✔
1106
                        struct spwd spwd;
432✔
1107

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

1113
                        if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
432✔
1114
                                r = nss_spwd_for_passwd(pw, &spwd, &buffer);
432✔
1115
                                if (r < 0) {
432✔
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);
432✔
1125
                        if (r < 0)
432✔
1126
                                return r;
1127

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

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

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

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

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

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

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

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

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

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

1163
                r = dropin_user_record_by_uid(uid, i, iterator->flags, ret);
40✔
1164
                if (r < 0) {
40✔
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 */
40✔
1172
                iterator->n_found++;
40✔
1173
                return 0;
40✔
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);
372✔
1178
        if (r < 0) {
372✔
1179

1180
                /* Finally, synthesize root + nobody if not done yet */
1181
                if (iterator->synthesize_root) {
52✔
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) {
47✔
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)
42✔
1195
                        return -ESRCH;
42✔
1196
        }
1197

1198
        return r;
1199
}
1200

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

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

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

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

1214
                if (ur && !user_record_match(ur, match))
802✔
1215
                        continue;
139✔
1216

1217
                if (ret)
663✔
1218
                        *ret = TAKE_PTR(ur);
662✔
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) {
8✔
1241
        assert(ret);
8✔
1242

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

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

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

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

1292
        assert(query);
36,331✔
1293

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

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

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

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

1314
        int r;
24,931✔
1315

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

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

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

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

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

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

1360
        assert(name);
29,680✔
1361

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

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

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

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

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

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

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

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

1404
        return 0;
1405
}
1406

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1486
        if (ret)
3,530✔
1487
                *ret = TAKE_PTR(gr);
3,530✔
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,641✔
1664
        int r;
12,641✔
1665

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

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

1681
        assert(ret);
1,511✔
1682

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

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

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

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

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

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

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

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

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

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

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

1726
        assert(ret);
25,854✔
1727

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

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

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

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

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

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

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

1759
                        iterator->index_members_of_group = 0;
9✔
1760

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

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

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

1775
        *ret = TAKE_PTR(iterator);
14,566✔
1776
        return 0;
14,566✔
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,949✔
1813
                UserDBIterator *iterator,
1814
                char **ret_user,
1815
                char **ret_group) {
1816

1817
        int r;
14,949✔
1818

1819
        assert(iterator);
14,949✔
1820

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

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

1829
                        assert(!iterator->found_user_name);
161✔
1830
                        do {
7,725✔
1831
                                errno = 0;
7,725✔
1832
                                g = getgrent();
7,725✔
1833
                                if (!g) {
7,725✔
1834
                                        if (errno != 0)
135✔
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,702✔
1840
                                                              strv_isempty(g->gr_mem));
224✔
1841

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

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

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

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

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

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

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

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

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

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

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

1895
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
14,915✔
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,915✔
1938
        if (r < 0 && iterator->n_found > 0)
14,915✔
1939
                return -ESRCH;
25✔
1940

1941
        return r;
1942
}
1943

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

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

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

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

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

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

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

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