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

systemd / systemd / 20151578145

11 Dec 2025 05:38AM UTC coverage: 72.698% (-0.02%) from 72.713%
20151578145

push

github

web-flow
core: gracefully skip unknown policy designators in RootImagePolicy et al (#40060)

Usually we gracefully ignore unknown configuration parameters, so that
service files can be written by upstreams and used across a variegated
range of distributions with various versions of systemd, to avoid
forcing users to the minimum common denominator and only adding settings
that are supported by the oldest distro supported.

Image policies do not behave like this, and any unknown partition or
policy designator causes the whole unit to fail to parse and a hard
error.

Change it so that parsing RootImagePolicy and friends via unit file or
D-Bus logs but otherwise ignores unknown specifiers, like other options
do.

This allows us to add new specifiers in the future, and users to adopt
them immediately.

Follow-up for d452335aa

44 of 49 new or added lines in 7 files covered. (89.8%)

297 existing lines in 37 files now uncovered.

309479 of 425707 relevant lines covered (72.7%)

1150153.67 hits per line

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

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

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

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

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

29
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, sd_varlink, sd_varlink_unref);
2,077✔
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,561✔
72
        if (!iterator)
80,561✔
73
                return NULL;
74

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

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

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

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

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

88
                break;
89

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

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

96
                break;
97

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

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

108
                break;
109

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

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

116
        if (iterator->nss_systemd_blocked)
80,463✔
117
                assert_se(userdb_block_nss_systemd(false) >= 0);
8,996✔
118

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

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

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

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

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

139
        return i;
80,463✔
140
}
141

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

145
        assert(iterator);
8,996✔
146

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

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

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

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

163
static void user_group_data_done(struct user_group_data *d) {
6,166✔
164
        sd_json_variant_unref(d->record);
6,166✔
165
}
6,166✔
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) {
233✔
173
        free(d->user_name);
233✔
174
        free(d->group_name);
233✔
175
}
233✔
176

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

183
        int r;
154,981✔
184

185
        assert(iterator);
154,981✔
186
        assert(link);
154,981✔
187
        assert(error_id);
154,981✔
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,042,743✔
205
        STRV_FOREACH(f, fields) {
1,042,743✔
206
                if (!sd_varlink_error_is_invalid_parameter(error_id, parameters, *f))
911,198✔
207
                        continue;
887,762✔
208

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

212
                restart = true;
213
                break;
214
        }
215

216
        if (!restart)
154,981✔
217
                return 0;
154,981✔
218

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

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

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

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

251
        if (error_id) {
161,380✔
252
                log_debug("Got lookup error: %s", error_id);
154,981✔
253

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

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

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

291
                static const sd_json_dispatch_field dispatch_table[] = {
3,296✔
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,880✔
297

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

429
finish:
5,560✔
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,541✔
434
            (r == -ESRCH && iterator->error != ENOEXEC) ||
131,536✔
435
            iterator->error == 0)
29,005✔
436
                iterator->error = -r;
155,784✔
437

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

443
static int userdb_connect(
162,626✔
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;
162,626✔
451
        int r;
162,626✔
452

453
        assert(iterator);
162,626✔
454
        assert(path);
162,626✔
455
        assert(method);
162,626✔
456

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

461
        sd_varlink_set_userdata(vl, iterator);
162,618✔
462

463
        if (!iterator->event) {
162,618✔
464
                r = sd_event_new(&iterator->event);
51,705✔
465
                if (r < 0)
51,705✔
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);
162,618✔
470
        if (r < 0)
162,618✔
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);
162,618✔
477
        if (r < 0)
162,618✔
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);
162,618✔
481
        if (r < 0)
162,618✔
482
                return log_debug_errno(r, "Failed to bind reply callback: %m");
×
483

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

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

495
        if (more)
162,618✔
496
                r = sd_varlink_observe(vl, method, patched_query);
47,632✔
497
        else
498
                r = sd_varlink_invoke(vl, method, patched_query);
114,986✔
499
        if (r < 0)
162,618✔
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));
162,618✔
503
        if (r < 0)
162,618✔
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,463✔
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,463✔
516
        _cleanup_closedir_ DIR *d = NULL;
80,463✔
517
        const char *e;
80,463✔
518
        int r, ret = 0;
80,463✔
519

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

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

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

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

543
        e = getenv("SYSTEMD_ONLY_USERDB");
51,713✔
544
        if (e) {
51,713✔
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,731✔
552
            !strv_contains(except, "io.systemd.Multiplexer") &&
10,018✔
553
            (!only || strv_contains(only, "io.systemd.Multiplexer"))) {
10,018✔
554
                r = userdb_connect(iterator, "/run/systemd/userdb/io.systemd.Multiplexer", method, more, query);
10,018✔
555
                if (r >= 0) {
10,018✔
556
                        iterator->nss_covered = true; /* The multiplexer does NSS */
10,018✔
557
                        iterator->dropin_covered = true; /* It also handles drop-in stuff */
10,018✔
558
                        return 0;
10,018✔
559
                }
560
        }
561

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

567
                return -errno;
×
568
        }
569

570
        FOREACH_DIRENT(de, d, return -errno) {
347,213✔
571
                _cleanup_free_ char *p = NULL;
129,172✔
572
                bool is_nss, is_dropin;
222,128✔
573

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

577
                if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
180,458✔
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");
180,452✔
586
                if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
180,452✔
587
                        continue;
41,598✔
588

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

594
                if (strv_contains(except, de->d_name))
129,332✔
595
                        continue;
160✔
596

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

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

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

611
                RET_GATHER(ret, r);
129,172✔
612
        }
613

614
        if (set_isempty(iterator->links))
41,695✔
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,809✔
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,809✔
629

630
        assert(iterator);
52,809✔
631

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

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

646
                        return 0;
3,296✔
647
                }
648

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

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

662
                        return 0;
2,870✔
663
                }
664

665
                if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
822,363✔
666
                        if (ret_user_name)
233✔
667
                                *ret_user_name = TAKE_PTR(iterator->found_user_name);
129✔
668
                        else
669
                                iterator->found_user_name = mfree(iterator->found_user_name);
104✔
670

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

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

681
                        return 0;
233✔
682
                }
683

684
                if (set_isempty(iterator->links)) {
822,130✔
685
                        if (iterator->error == 0)
46,410✔
686
                                return -ESRCH;
687

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

776
        assert(name);
13,342✔
777
        assert(ret_uid);
13,342✔
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,342✔
783
        if (!e)
13,342✔
784
                goto nomatch;
13,329✔
785

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

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

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

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

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

806
        assert(query);
16,058✔
807

808
        uint64_t mask = match->disposition_mask;
16,058✔
809
        if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL)) {
16,058✔
810
                for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
65,072✔
811
                        if (!BIT_SET(mask, d))
56,938✔
812
                                continue;
×
813

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

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

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

830
        assert(query);
13,351✔
831

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

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

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

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

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

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

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

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

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

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

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

891
        return -ESRCH;
892
}
893

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

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

907
        assert(name);
7,816✔
908

909
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
7,816✔
910
                uid_t uid;
3,409✔
911

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

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

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

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

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

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

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

951
        if (ret)
3,910✔
952
                *ret = TAKE_PTR(ur);
3,910✔
953

954
        return 0;
955
}
956

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

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

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

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

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

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

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

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

998
        return -ESRCH;
999
}
1000

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

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

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

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

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

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

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

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

1040
        return 0;
1041
}
1042

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

1048
        assert(ret);
27✔
1049

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1201
        return r;
1202
}
1203

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

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

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

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

1217
                if (ur && !user_record_match(ur, match))
523✔
1218
                        continue;
88✔
1219

1220
                if (ret)
435✔
1221
                        *ret = TAKE_PTR(ur);
434✔
1222

1223
                return r;
1224
        }
1225
}
1226

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1295
        assert(query);
38,231✔
1296

1297
        if (!userdb_match_is_set(match))
38,231✔
1298
                return 0;
1299

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

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

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

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

1317
        int r;
26,199✔
1318

1319
        assert(name);
26,199✔
1320
        assert(iterator);
26,199✔
1321
        assert(ret);
26,199✔
1322

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

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

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

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

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

1355
        return -ESRCH;
1356
}
1357

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

1363
        assert(name);
31,065✔
1364

1365
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
31,065✔
1366
                gid_t gid;
4,825✔
1367

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

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

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

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

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

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

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

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

1407
        return 0;
1408
}
1409

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

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

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

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

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

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

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

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

1450
        return -ESRCH;
1451
}
1452

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

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

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

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

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

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

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

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

1492
        return 0;
1493
}
1494

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

1500
        assert(ret);
11✔
1501

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1640
        return r;
1641
}
1642

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

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

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

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

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

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

1662
                return r;
1663
        }
1664
}
1665

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

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

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

1684
        assert(ret);
1,670✔
1685

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

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

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

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

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

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

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

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

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

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

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

1729
        assert(ret);
27,213✔
1730

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

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

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

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

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

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

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

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

1762
                        iterator->index_members_of_group = 0;
×
1763

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

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

1773
        if (qr < 0 &&
27,211✔
1774
            strv_isempty(iterator->members_of_group) &&
12,027✔
1775
            strv_isempty(iterator->dropins))
27,213✔
1776
                return qr;
1777

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

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

1786
        assert(ret);
2✔
1787

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

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

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

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

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

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

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

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

1820
        int r;
15,752✔
1821

1822
        assert(iterator);
15,752✔
1823

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

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

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

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

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

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

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

UNCOV
1865
                assert(iterator->found_group_name);
×
UNCOV
1866
                assert(iterator->members_of_group);
×
UNCOV
1867
                assert(!iterator->found_user_name);
×
1868

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

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

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

UNCOV
1884
                        if (ret_user)
×
UNCOV
1885
                                *ret_user = TAKE_PTR(cu);
×
1886

UNCOV
1887
                        if (ret_group)
×
UNCOV
1888
                                *ret_group = TAKE_PTR(cg);
×
1889

UNCOV
1890
                        iterator->index_members_of_group++;
×
UNCOV
1891
                        return 0;
×
1892
                }
1893

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

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

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

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

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

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

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

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

1937
                return 0;
1938
        }
1939

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

1944
        return r;
1945
}
1946

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

1952
        assert(name);
15,159✔
1953
        assert(ret);
15,159✔
1954

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

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

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

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

1973
        strv_sort_uniq(members);
15,158✔
1974

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

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

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

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

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

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

2001
        return call(b);
18,605✔
2002
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc