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

systemd / systemd / 18297936107

06 Oct 2025 02:12PM UTC coverage: 72.276% (+0.03%) from 72.246%
18297936107

push

github

poettering
bootspec: also process uki-url boot loader spec field

Let's also add support for "uki-url", which was added at the same time to
the spec as "uki".

Follow-up for: 4a94a1b83

2 of 5 new or added lines in 1 file covered. (40.0%)

171 existing lines in 30 files now uncovered.

303492 of 419906 relevant lines covered (72.28%)

1100392.07 hits per line

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

88.0
/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,705✔
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) {
80,172✔
72
        if (!iterator)
80,172✔
73
                return NULL;
74

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

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

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

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

85
                if (iterator->nss_iterating)
12,302✔
86
                        endpwent();
×
87

88
                break;
89

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

93
                if (iterator->nss_iterating)
38,619✔
94
                        endgrent();
×
95

96
                break;
97

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

105
                if (iterator->nss_iterating)
29,100✔
106
                        endgrent();
×
107

108
                break;
109

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

114
        sd_event_unref(iterator->event);
80,021✔
115

116
        if (iterator->nss_systemd_blocked)
80,021✔
117
                assert_se(userdb_block_nss_systemd(false) >= 0);
9,069✔
118

119
        return mfree(iterator);
80,021✔
120
}
121

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

125
        assert(what >= 0);
80,021✔
126
        assert(what < _LOOKUP_WHAT_MAX);
80,021✔
127

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

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

139
        return i;
80,021✔
140
}
141

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

145
        assert(iterator);
9,069✔
146

147
        if (iterator->nss_systemd_blocked)
9,069✔
148
                return 0;
149

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

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

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

163
static void user_group_data_done(struct user_group_data *d) {
5,428✔
164
        sd_json_variant_unref(d->record);
5,428✔
165
}
5,428✔
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(
155,164✔
178
                UserDBIterator *iterator,
179
                sd_varlink *link,
180
                sd_json_variant *parameters,
181
                const char *error_id) {
182

183
        int r;
155,164✔
184

185
        assert(iterator);
155,164✔
186
        assert(link);
155,164✔
187
        assert(error_id);
155,164✔
188

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

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

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

212
                restart = true;
213
                break;
214
        }
215

216
        if (!restart)
155,164✔
217
                return 0;
155,164✔
218

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

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

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

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

251
        if (error_id) {
160,627✔
252
                log_debug("Got lookup error: %s", error_id);
155,164✔
253

254
                r = userdb_maybe_restart_query(iterator, link, parameters, error_id);
155,164✔
255
                if (r < 0)
155,164✔
256
                        return r;
257
                if (r > 0) {
155,164✔
258
                        r = 0;
23,372✔
259
                        goto finish;
23,372✔
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,
131,792✔
267
                               "io.systemd.UserDatabase.NoRecordFound",
268
                               "io.systemd.UserDatabase.ConflictingRecordFound") ||
44✔
269
                    sd_varlink_error_is_invalid_parameter(error_id, parameters, "userName") ||
88✔
270
                    sd_varlink_error_is_invalid_parameter(error_id, parameters, "groupName"))
44✔
271
                        r = -ESRCH;
131,748✔
272
                else if (streq(error_id, "io.systemd.UserDatabase.NonMatchingRecordFound"))
44✔
273
                        r = -ENOEXEC;
274
                else if (streq(error_id, "io.systemd.UserDatabase.ServiceNotAvailable"))
32✔
275
                        r = -EHOSTDOWN;
276
                else if (streq(error_id, "io.systemd.UserDatabase.EnumerationNotSupported"))
32✔
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;
131,792✔
284
        }
285

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

391
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
2,641✔
392
                        return 0;
278✔
393

394
                r = 0;
2,363✔
395
                goto finish;
2,363✔
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,856✔
430
        /* If we got one ENOEXEC, let that win. Similarly, ESRCH wins except for ENOEXEC. This way when we do
431
         * a wild dump we won't be tripped up by bad errors – as long as at least one connection ended
432
         * somewhat cleanly. */
433
        if (r == -ENOEXEC ||
160,020✔
434
            (r == -ESRCH && iterator->error != ENOEXEC) ||
131,748✔
435
            iterator->error == 0)
28,260✔
436
                iterator->error = -r;
153,733✔
437

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

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

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

453
        assert(iterator);
161,733✔
454
        assert(path);
161,733✔
455
        assert(method);
161,733✔
456

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

461
        sd_varlink_set_userdata(vl, iterator);
161,725✔
462

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

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

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

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

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

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

495
        if (more)
161,725✔
496
                r = sd_varlink_observe(vl, method, patched_query);
48,217✔
497
        else
498
                r = sd_varlink_invoke(vl, method, patched_query);
113,508✔
499
        if (r < 0)
161,725✔
500
                return log_debug_errno(r, "Failed to invoke varlink method: %m");
×
501

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

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

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

520
        assert(iterator);
80,021✔
521
        assert(method);
80,021✔
522

523
        if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
80,021✔
524
                return -ENOLINK;
525

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

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

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

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

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

567
                return -errno;
×
568
        }
569

570
        FOREACH_DIRENT(de, d, return -errno) {
346,975✔
571
                _cleanup_free_ char *p = NULL;
128,426✔
572
                bool is_nss, is_dropin;
221,536✔
573

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

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

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

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

594
                if (strv_contains(except, de->d_name))
128,538✔
595
                        continue;
112✔
596

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

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

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

611
                RET_GATHER(ret, r);
128,426✔
612
        }
613

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

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

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

628
        int r;
52,427✔
629

630
        assert(iterator);
52,427✔
631

632
        for (;;) {
825,580✔
633
                if (iterator->what == LOOKUP_USER && iterator->found_user) {
825,580✔
634
                        if (ret_user_record)
2,787✔
635
                                *ret_user_record = TAKE_PTR(iterator->found_user);
2,787✔
636
                        else
637
                                iterator->found_user = user_record_unref(iterator->found_user);
×
638

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

646
                        return 0;
2,787✔
647
                }
648

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

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

662
                        return 0;
2,641✔
663
                }
664

665
                if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
820,152✔
666
                        if (ret_user_name)
35✔
667
                                *ret_user_name = TAKE_PTR(iterator->found_user_name);
21✔
668
                        else
669
                                iterator->found_user_name = mfree(iterator->found_user_name);
14✔
670

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

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

681
                        return 0;
35✔
682
                }
683

684
                if (set_isempty(iterator->links)) {
820,117✔
685
                        if (iterator->error == 0)
46,964✔
686
                                return -ESRCH;
687

688
                        return -abs(iterator->error);
46,901✔
689
                }
690

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

776
        assert(name);
13,618✔
777
        assert(ret_uid);
13,618✔
778

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

782
        const char *e = startswith(name, "foreign-");
13,618✔
783
        if (!e)
13,618✔
784
                goto nomatch;
13,616✔
785

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

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

UNCOV
794
        *ret_uid = uid;
×
UNCOV
795
        return 1;
×
796

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

802
static int query_append_disposition_mask(sd_json_variant **query, uint64_t mask) {
16,090✔
803
        int r;
16,090✔
804

805
        assert(query);
16,090✔
806

807
        if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL))
16,090✔
808
                return 0;
16,090✔
809

810
        _cleanup_strv_free_ char **dispositions = NULL;
16,082✔
811
        for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
128,656✔
812
                if (!BITS_SET(mask, d))
225,148✔
813
                        continue;
80,423✔
814

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

820
        return sd_json_variant_merge_objectbo(
16,082✔
821
                        query,
822
                        SD_JSON_BUILD_PAIR_STRV("dispositionMask", dispositions));
823
}
824

825
static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *match) {
12,302✔
826
        int r;
12,302✔
827

828
        assert(query);
12,302✔
829

830
        if (!userdb_match_is_set(match))
12,302✔
831
                return 0;
832

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

841
        return query_append_disposition_mask(query, match->disposition_mask);
6,271✔
842
}
843

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

851
        assert(name);
2,634✔
852
        assert(iterator);
2,634✔
853
        assert(ret);
2,634✔
854

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

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

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

873
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
830✔
874
                if (streq(name, "root"))
305✔
875
                        return synthetic_root_user_build(ret);
1✔
876

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

881
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
829✔
882
                uid_t foreign_uid;
304✔
883
                r = user_name_foreign_extract_uid(name, &foreign_uid);
304✔
884
                if (r < 0)
304✔
UNCOV
885
                        return r;
×
886
                if (r > 0)
304✔
UNCOV
887
                        return synthetic_foreign_user_build(foreign_uid, ret);
×
888
        }
889

890
        return -ESRCH;
891
}
892

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

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

906
        assert(name);
7,136✔
907

908
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
7,136✔
909
                uid_t uid;
3,411✔
910

911
                if (parse_uid(name, &uid) >= 0)
3,411✔
912
                        return userdb_by_uid(uid, match, flags, ret);
3,214✔
913
        }
914

915
        if (!valid_user_group_name(name, VALID_USER_RELAX))
3,922✔
916
                return -EINVAL;
917

918
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
3,915✔
919
        if (r < 0)
3,915✔
920
                return r;
921

922
        r = query_append_uid_match(&query, match);
3,915✔
923
        if (r < 0)
3,915✔
924
                return r;
925

926
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
3,915✔
927
        if (!iterator)
3,915✔
928
                return -ENOMEM;
929

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

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

950
        if (ret)
3,080✔
951
                *ret = TAKE_PTR(ur);
3,080✔
952

953
        return 0;
954
}
955

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

963
        assert(uid_is_valid(uid));
7,171✔
964
        assert(iterator);
7,171✔
965
        assert(ret);
7,171✔
966

967
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
7,171✔
968
                r = dropin_user_record_by_uid(uid, NULL, flags, ret);
3,963✔
969
                if (r >= 0)
3,963✔
970
                        return r;
971
        }
972

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

983
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
6,516✔
984
                if (uid == 0)
6,234✔
985
                        return synthetic_root_user_build(ret);
1✔
986

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

991
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && uid_is_foreign(uid))
6,514✔
992
                return synthetic_foreign_user_build(uid - FOREIGN_UID_BASE, ret);
3✔
993

994
        if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
6,511✔
995
                return synthetic_numeric_user_build(uid, ret);
2,934✔
996

997
        return -ESRCH;
998
}
999

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

1005
        if (!uid_is_valid(uid))
8,344✔
1006
                return -EINVAL;
1007

1008
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(uid)));
8,344✔
1009
        if (r < 0)
8,344✔
1010
                return r;
1011

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

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

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

1033
        if (!user_record_match(ur, match))
4,760✔
1034
                return -ENOEXEC;
1035

1036
        if (ret)
4,757✔
1037
                *ret = TAKE_PTR(ur);
4,757✔
1038

1039
        return 0;
1040
}
1041

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

1047
        assert(ret);
43✔
1048

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

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

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

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

1064
                setpwent();
18✔
1065
                iterator->nss_iterating = true;
18✔
1066
        }
1067

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

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

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

1087
        *ret = TAKE_PTR(iterator);
43✔
1088
        return 0;
43✔
1089
}
1090

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

1094
        assert(iterator);
863✔
1095
        assert(iterator->what == LOOKUP_USER);
863✔
1096

1097
        if (iterator->nss_iterating) {
863✔
1098
                struct passwd *pw;
450✔
1099

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

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

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

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

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

1130
                        if (ret)
432✔
1131
                                (*ret)->incomplete = incomplete;
432✔
1132

1133
                        iterator->n_found++;
432✔
1134
                        return r;
432✔
1135
                }
1136

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

1140
                iterator->nss_iterating = false;
18✔
1141
                endpwent();
18✔
1142
        }
1143

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

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

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

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

1160
                *e = 0; /* Chop off suffix */
76✔
1161

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

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

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

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

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

1189
                if (iterator->synthesize_nobody) {
48✔
1190
                        iterator->synthesize_nobody = false;
6✔
1191
                        iterator->n_found++;
6✔
1192
                        return synthetic_nobody_user_build(ret);
6✔
1193
                }
1194

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

1200
        return r;
1201
}
1202

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

1206
        assert(iterator);
744✔
1207
        assert(iterator->what == LOOKUP_USER);
744✔
1208

1209
        for (;;) {
119✔
1210
                _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
744✔
1211

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

1216
                if (ur && !user_record_match(ur, match))
821✔
1217
                        continue;
119✔
1218

1219
                if (ret)
702✔
1220
                        *ret = TAKE_PTR(ur);
701✔
1221

1222
                return r;
1223
        }
1224
}
1225

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

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

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

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

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

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

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

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

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

1272
        if (!gid_is_system(gid))
2,838✔
1273
                return -ESRCH;
1274

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

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

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

1291
static int query_append_gid_match(sd_json_variant **query, const UserDBMatch *match) {
38,619✔
1292
        int r;
38,619✔
1293

1294
        assert(query);
38,619✔
1295

1296
        if (!userdb_match_is_set(match))
38,619✔
1297
                return 0;
1298

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

1307
        return query_append_disposition_mask(query, match->disposition_mask);
9,819✔
1308
}
1309

1310
static int groupdb_by_name_fallbacks(
26,525✔
1311
                const char *name,
1312
                UserDBIterator *iterator,
1313
                UserDBFlags flags,
1314
                GroupRecord **ret) {
1315

1316
        int r;
26,525✔
1317

1318
        assert(name);
26,525✔
1319
        assert(iterator);
26,525✔
1320
        assert(ret);
26,525✔
1321

1322
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
26,525✔
1323
                r = dropin_group_record_by_name(name, NULL, flags, ret);
13,322✔
1324
                if (r >= 0)
13,322✔
1325
                        return r;
1326
        }
1327

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

1337
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
24,486✔
1338
                if (streq(name, "root"))
13,315✔
1339
                        return synthetic_root_group_build(ret);
1✔
1340

1341
                if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
13,314✔
1342
                        return synthetic_nobody_group_build(ret);
×
1343
        }
1344

1345
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
24,485✔
1346
                uid_t foreign_gid;
13,314✔
1347
                r = user_name_foreign_extract_uid(name, &foreign_gid); /* Same for UID + GID */
13,314✔
1348
                if (r < 0)
13,314✔
1349
                        return r;
×
1350
                if (r > 0)
13,314✔
1351
                        return synthetic_foreign_group_build(foreign_gid, ret);
×
1352
        }
1353

1354
        return -ESRCH;
1355
}
1356

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

1362
        assert(name);
31,393✔
1363

1364
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
31,393✔
1365
                gid_t gid;
4,857✔
1366

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

1371
        if (!valid_user_group_name(name, VALID_USER_RELAX))
28,377✔
1372
                return -EINVAL;
1373

1374
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
28,372✔
1375
        if (r < 0)
28,372✔
1376
                return r;
1377

1378
        r = query_append_gid_match(&query, match);
28,372✔
1379
        if (r < 0)
28,372✔
1380
                return r;
1381

1382
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
28,372✔
1383
        if (!iterator)
28,372✔
1384
                return -ENOMEM;
1385

1386
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
28,372✔
1387
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
28,372✔
1388
        if (r >= 0) {
28,372✔
1389
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
17,195✔
1390
                if (r == -ENOEXEC)
17,195✔
1391
                        return r;
1392
        }
1393
        if (r < 0) {
17,192✔
1394
                r = groupdb_by_name_fallbacks(name, iterator, flags, &gr);
26,525✔
1395
                if (r < 0)
26,525✔
1396
                        return r;
1397
        }
1398

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

1403
        if (ret)
3,882✔
1404
                *ret = TAKE_PTR(gr);
3,882✔
1405

1406
        return 0;
1407
}
1408

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

1416
        assert(gid_is_valid(gid));
9,713✔
1417
        assert(iterator);
9,713✔
1418
        assert(ret);
9,713✔
1419

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

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

1435
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
9,451✔
1436
                if (gid == 0)
8,272✔
1437
                        return synthetic_root_group_build(ret);
1✔
1438

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

1443
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && gid_is_foreign(gid))
9,449✔
1444
                return synthetic_foreign_group_build(gid - FOREIGN_UID_BASE, ret);
2✔
1445

1446
        if (FLAGS_SET(flags, USERDB_SYNTHESIZE_NUMERIC))
9,447✔
1447
                return synthetic_numeric_group_build(gid, ret);
2,838✔
1448

1449
        return -ESRCH;
1450
}
1451

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

1457
        if (!gid_is_valid(gid))
10,227✔
1458
                return -EINVAL;
1459

1460
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(gid)));
10,227✔
1461
        if (r < 0)
10,227✔
1462
                return r;
1463

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

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

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

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

1488
        if (ret)
3,610✔
1489
                *ret = TAKE_PTR(gr);
3,610✔
1490

1491
        return 0;
1492
}
1493

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

1499
        assert(ret);
20✔
1500

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

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

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

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

1516
                setgrent();
9✔
1517
                iterator->nss_iterating = true;
9✔
1518
        }
1519

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

1531
        if (qr < 0 &&
20✔
1532
            !iterator->nss_iterating &&
1✔
1533
            strv_isempty(iterator->dropins))
21✔
1534
                return qr;
1535

1536
        *ret = TAKE_PTR(iterator);
20✔
1537
        return 0;
20✔
1538
}
1539

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

1543
        assert(iterator);
841✔
1544
        assert(iterator->what == LOOKUP_GROUP);
841✔
1545

1546
        if (iterator->nss_iterating) {
841✔
1547
                struct group *gr;
513✔
1548

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

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

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

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

1576
                        if (ret)
504✔
1577
                                (*ret)->incomplete = incomplete;
504✔
1578

1579
                        iterator->n_found++;
504✔
1580
                        return r;
504✔
1581
                }
1582

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

1586
                iterator->nss_iterating = false;
9✔
1587
                endgrent();
9✔
1588
        }
1589

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

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

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

1604
                *e = 0; /* Chop off suffix */
38✔
1605

1606
                if (parse_gid(fn, &gid) < 0)
38✔
1607
                        continue;
19✔
1608

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

1615
                iterator->current_dropin++;
19✔
1616
                iterator->n_found++;
19✔
1617
                return 0;
19✔
1618
        }
1619

1620
        r = userdb_process(iterator, NULL, ret, NULL, NULL);
318✔
1621
        if (r < 0) {
318✔
1622
                if (iterator->synthesize_root) {
32✔
1623
                        iterator->synthesize_root = false;
6✔
1624
                        iterator->n_found++;
6✔
1625
                        return synthetic_root_group_build(ret);
6✔
1626
                }
1627

1628
                if (iterator->synthesize_nobody) {
26✔
1629
                        iterator->synthesize_nobody = false;
6✔
1630
                        iterator->n_found++;
6✔
1631
                        return synthetic_nobody_group_build(ret);
6✔
1632
                }
1633

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

1639
        return r;
1640
}
1641

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

1645
        assert(iterator);
594✔
1646
        assert(iterator->what == LOOKUP_GROUP);
594✔
1647

1648
        for (;;) {
247✔
1649
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
594✔
1650

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

1655
                if (gr && !group_record_match(gr, match))
821✔
1656
                        continue;
247✔
1657

1658
                if (ret)
574✔
1659
                        *ret = TAKE_PTR(gr);
574✔
1660

1661
                return r;
1662
        }
1663
}
1664

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

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

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

1683
        assert(ret);
1,599✔
1684

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

1688
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
1,597✔
1689
        if (r < 0)
1,597✔
1690
                return r;
1691

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

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

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

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

1707
                setgrent();
117✔
1708
                iterator->nss_iterating = true;
117✔
1709
        }
1710

1711
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1,597✔
1712
                discover_membership_dropins(iterator, flags);
1,412✔
1713

1714
        if (qr < 0 &&
1,597✔
1715
            !iterator->nss_iterating &&
1,295✔
1716
            strv_isempty(iterator->dropins))
1,599✔
1717
                return qr;
1718

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

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

1728
        assert(ret);
27,501✔
1729

1730
        if (!valid_user_group_name(name, VALID_USER_RELAX))
27,501✔
1731
                return -EINVAL;
1732

1733
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
27,499✔
1734
        if (r < 0)
27,499✔
1735
                return r;
1736

1737
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
27,499✔
1738
        if (!iterator)
27,499✔
1739
                return -ENOMEM;
1740

1741
        iterator->filter_group_name = strdup(name);
27,499✔
1742
        if (!iterator->filter_group_name)
27,499✔
1743
                return -ENOMEM;
1744

1745
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
27,499✔
1746

1747
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
27,499✔
1748
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
20✔
1749

1750
                r = userdb_iterator_block_nss_systemd(iterator);
20✔
1751
                if (r < 0)
20✔
1752
                        return r;
1753

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

1761
                        iterator->index_members_of_group = 0;
8✔
1762

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

1769
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
27,499✔
1770
                discover_membership_dropins(iterator, flags);
12,056✔
1771

1772
        if (qr < 0 &&
27,499✔
1773
            strv_isempty(iterator->members_of_group) &&
12,035✔
1774
            strv_isempty(iterator->dropins))
27,501✔
1775
                return qr;
1776

1777
        *ret = TAKE_PTR(iterator);
15,464✔
1778
        return 0;
15,464✔
1779
}
1780

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

1785
        assert(ret);
4✔
1786

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

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

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

1798
                setgrent();
2✔
1799
                iterator->nss_iterating = true;
2✔
1800
        }
1801

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

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

1810
        *ret = TAKE_PTR(iterator);
4✔
1811
        return 0;
4✔
1812
}
1813

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

1819
        int r;
15,834✔
1820

1821
        assert(iterator);
15,834✔
1822

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

1828
                        if (!iterator->nss_iterating)
15,827✔
1829
                                break;
1830

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

1841
                        } while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
6,776✔
1842
                                                              strv_isempty(g->gr_mem));
224✔
1843

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

1849
                                if (iterator->filter_user_name)
22✔
1850
                                        iterator->members_of_group = strv_new(iterator->filter_user_name);
12✔
1851
                                else
1852
                                        iterator->members_of_group = strv_copy(g->gr_mem);
10✔
1853
                                if (!iterator->members_of_group)
22✔
1854
                                        return -ENOMEM;
1855

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

1864
                assert(iterator->found_group_name);
59✔
1865
                assert(iterator->members_of_group);
59✔
1866
                assert(!iterator->found_user_name);
59✔
1867

1868
                if (iterator->members_of_group[iterator->index_members_of_group]) {
59✔
1869
                        _cleanup_free_ char *cu = NULL, *cg = NULL;
29✔
1870

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

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

1883
                        if (ret_user)
29✔
1884
                                *ret_user = TAKE_PTR(cu);
29✔
1885

1886
                        if (ret_group)
29✔
1887
                                *ret_group = TAKE_PTR(cg);
29✔
1888

1889
                        iterator->index_members_of_group++;
29✔
1890
                        return 0;
29✔
1891
                }
1892

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

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

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

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

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

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

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

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

1936
                return 0;
1937
        }
1938

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

1943
        return r;
1944
}
1945

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

1951
        assert(name);
15,419✔
1952
        assert(ret);
15,419✔
1953

1954
        r = membershipdb_by_group(name, flags, &iterator);
15,419✔
1955
        if (r < 0)
15,419✔
1956
                return r;
1957

1958
        for (;;) {
15,418✔
1959
                _cleanup_free_ char *user_name = NULL;
×
1960

1961
                r = membershipdb_iterator_get(iterator, &user_name, NULL);
15,418✔
1962
                if (r == -ESRCH)
15,418✔
1963
                        break;
1964
                if (r < 0)
×
1965
                        return r;
1966

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

1972
        strv_sort_uniq(members);
15,418✔
1973

1974
        *ret = TAKE_PTR(members);
15,418✔
1975
        return 0;
15,418✔
1976
}
1977

1978
int userdb_block_nss_systemd(int b) {
18,737✔
1979
        _cleanup_(dlclosep) void *dl = NULL;
18,737✔
1980
        int (*call)(bool b);
18,737✔
1981

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

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

1991
        log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2");
18,737✔
1992

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

1999
        return call(b);
18,737✔
2000
}
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