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

systemd / systemd / 14072511923

25 Mar 2025 07:34PM UTC coverage: 71.954% (+0.03%) from 71.927%
14072511923

push

github

web-flow
tools/check-version-history: avoid DeprecationWarning with newer lxml (#36860)

We get the same warning thousands of times:
/work/src/tools/check-version-history.py:28: FutureWarning: This search
incorrectly ignores the root element, and will be fixed in a future
version. If you rely on the current behaviour, change it to

"./refsynopsisdiv/funcsynopsis/funcprototype/funcdef/function[.='udev_device_get_properties_list_entry']"

We also need to update the ignorelist to the new form.

296652 of 412279 relevant lines covered (71.95%)

716218.11 hits per line

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

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

3
#include <sys/auxv.h>
4

5
#include "sd-varlink.h"
6

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

26
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, sd_varlink, sd_varlink_unref);
1,131✔
27

28
typedef enum LookupWhat {
29
        LOOKUP_USER,
30
        LOOKUP_GROUP,
31
        LOOKUP_MEMBERSHIP,
32
        _LOOKUP_WHAT_MAX,
33
} LookupWhat;
34

35
struct UserDBIterator {
36
        LookupWhat what;
37
        UserDBFlags flags;
38
        Set *links;
39

40
        const char *method; /* Note, this is a const static string! */
41
        sd_json_variant *query;
42

43
        bool more:1;
44
        bool nss_covered:1;
45
        bool nss_iterating:1;
46
        bool dropin_covered:1;
47
        bool synthesize_root:1;
48
        bool synthesize_nobody:1;
49
        bool nss_systemd_blocked:1;
50

51
        char **dropins;
52
        size_t current_dropin;
53
        int error;
54
        unsigned n_found;
55

56
        sd_event *event;
57
        UserRecord *found_user;                   /* when .what == LOOKUP_USER */
58
        GroupRecord *found_group;                 /* when .what == LOOKUP_GROUP */
59

60
        char *found_user_name, *found_group_name; /* when .what == LOOKUP_MEMBERSHIP */
61
        char **members_of_group;
62
        size_t index_members_of_group;
63
        char *filter_user_name, *filter_group_name;
64
};
65

66
static int userdb_connect(UserDBIterator *iterator, const char *path, const char *method, bool more, sd_json_variant *query);
67

68
UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
83,250✔
69
        if (!iterator)
83,250✔
70
                return NULL;
71

72
        sd_json_variant_unref(iterator->query);
83,091✔
73

74
        set_free(iterator->links);
83,091✔
75
        strv_free(iterator->dropins);
83,091✔
76

77
        switch (iterator->what) {
83,091✔
78

79
        case LOOKUP_USER:
4,563✔
80
                user_record_unref(iterator->found_user);
4,563✔
81

82
                if (iterator->nss_iterating)
4,563✔
83
                        endpwent();
×
84

85
                break;
86

87
        case LOOKUP_GROUP:
38,972✔
88
                group_record_unref(iterator->found_group);
38,972✔
89

90
                if (iterator->nss_iterating)
38,972✔
91
                        endgrent();
×
92

93
                break;
94

95
        case LOOKUP_MEMBERSHIP:
39,556✔
96
                free(iterator->found_user_name);
39,556✔
97
                free(iterator->found_group_name);
39,556✔
98
                strv_free(iterator->members_of_group);
39,556✔
99
                free(iterator->filter_user_name);
39,556✔
100
                free(iterator->filter_group_name);
39,556✔
101

102
                if (iterator->nss_iterating)
39,556✔
103
                        endgrent();
×
104

105
                break;
106

107
        default:
×
108
                assert_not_reached();
×
109
        }
110

111
        sd_event_unref(iterator->event);
83,091✔
112

113
        if (iterator->nss_systemd_blocked)
83,091✔
114
                assert_se(userdb_block_nss_systemd(false) >= 0);
831✔
115

116
        return mfree(iterator);
83,091✔
117
}
118

119
static UserDBIterator* userdb_iterator_new(LookupWhat what, UserDBFlags flags) {
83,091✔
120
        UserDBIterator *i;
83,091✔
121

122
        assert(what >= 0);
83,091✔
123
        assert(what < _LOOKUP_WHAT_MAX);
83,091✔
124

125
        i = new(UserDBIterator, 1);
83,091✔
126
        if (!i)
83,091✔
127
                return NULL;
128

129
        *i = (UserDBIterator) {
83,091✔
130
                .what = what,
131
                .flags = flags,
132
                .synthesize_root = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC),
83,091✔
133
                .synthesize_nobody = !FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC),
134
        };
135

136
        return i;
83,091✔
137
}
138

139
static int userdb_iterator_block_nss_systemd(UserDBIterator *iterator) {
831✔
140
        int r;
831✔
141

142
        assert(iterator);
831✔
143

144
        if (iterator->nss_systemd_blocked)
831✔
145
                return 0;
146

147
        r = userdb_block_nss_systemd(true);
831✔
148
        if (r < 0)
831✔
149
                return r;
150

151
        iterator->nss_systemd_blocked = true;
831✔
152
        return 1;
831✔
153
}
154

155
struct user_group_data {
156
        sd_json_variant *record;
157
        bool incomplete;
158
};
159

160
static void user_group_data_done(struct user_group_data *d) {
2,605✔
161
        sd_json_variant_unref(d->record);
2,605✔
162
}
2,605✔
163

164
struct membership_data {
165
        char *user_name;
166
        char *group_name;
167
};
168

169
static void membership_data_done(struct membership_data *d) {
35✔
170
        free(d->user_name);
35✔
171
        free(d->group_name);
35✔
172
}
35✔
173

174
static int userdb_maybe_restart_query(
148,505✔
175
                UserDBIterator *iterator,
176
                sd_varlink *link,
177
                sd_json_variant *parameters,
178
                const char *error_id) {
179

180
        int r;
148,505✔
181

182
        assert(iterator);
148,505✔
183
        assert(link);
148,505✔
184
        assert(error_id);
148,505✔
185

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

199
        /* Figure out if the reported error indicates any of the suppressible fields are at fault, and that
200
         * our query actually included them */
201
        bool restart = false;
1,039,361✔
202
        STRV_FOREACH(f, fields) {
1,039,361✔
203
                if (!sd_varlink_error_is_invalid_parameter(error_id, parameters, *f))
890,892✔
204
                        continue;
890,856✔
205

206
                if (!sd_json_variant_by_key(iterator->query, *f))
36✔
207
                        continue;
×
208

209
                restart = true;
210
                break;
211
        }
212

213
        if (!restart)
148,505✔
214
                return 0;
148,505✔
215

216
        /* Now patch the fields out */
217
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query =
148,505✔
218
                sd_json_variant_ref(iterator->query);
36✔
219

220
        r = sd_json_variant_filter(&patched_query, (char**const) fields);
36✔
221
        if (r < 0)
36✔
222
                return r;
223

224
        /* NB: we stored the socket path in the varlink connection description when we set things up here! */
225
        r = userdb_connect(
108✔
226
                        iterator,
227
                        ASSERT_PTR(sd_varlink_get_description(link)),
36✔
228
                        iterator->method,
229
                        iterator->more,
36✔
230
                        patched_query);
231
        if (r < 0)
36✔
232
                return r;
233

234
        log_debug("Restarted query to service '%s' due to missing features.", sd_varlink_get_description(link));
36✔
235
        return 1;
236
}
237

238
static int userdb_on_query_reply(
151,145✔
239
                sd_varlink *link,
240
                sd_json_variant *parameters,
241
                const char *error_id,
242
                sd_varlink_reply_flags_t flags,
243
                void *userdata) {
244

245
        UserDBIterator *iterator = ASSERT_PTR(userdata);
151,145✔
246
        int r;
151,145✔
247

248
        if (error_id) {
151,145✔
249
                log_debug("Got lookup error: %s", error_id);
148,505✔
250

251
                r = userdb_maybe_restart_query(iterator, link, parameters, error_id);
148,505✔
252
                if (r < 0)
148,505✔
253
                        return r;
254
                if (r > 0) {
148,505✔
255
                        r = 0;
36✔
256
                        goto finish;
36✔
257
                }
258

259
                /* Convert various forms of record not found into -ESRCH, since NSS typically doesn't care,
260
                 * about the details. Note that if a userName specification is refused as invalid parameter,
261
                 * we also turn this into -ESRCH following the logic that there cannot be a user record for a
262
                 * completely invalid user name. */
263
                if (STR_IN_SET(error_id,
148,469✔
264
                               "io.systemd.UserDatabase.NoRecordFound",
265
                               "io.systemd.UserDatabase.ConflictingRecordFound") ||
30✔
266
                    sd_varlink_error_is_invalid_parameter(error_id, parameters, "userName") ||
60✔
267
                    sd_varlink_error_is_invalid_parameter(error_id, parameters, "groupName"))
30✔
268
                        r = -ESRCH;
148,439✔
269
                else if (streq(error_id, "io.systemd.UserDatabase.NonMatchingRecordFound"))
30✔
270
                        r = -ENOEXEC;
271
                else if (streq(error_id, "io.systemd.UserDatabase.ServiceNotAvailable"))
30✔
272
                        r = -EHOSTDOWN;
273
                else if (streq(error_id, "io.systemd.UserDatabase.EnumerationNotSupported"))
30✔
274
                        r = -EOPNOTSUPP;
275
                else if (streq(error_id, SD_VARLINK_ERROR_TIMEOUT))
×
276
                        r = -ETIMEDOUT;
277
                else
278
                        r = -EIO;
×
279

280
                goto finish;
148,469✔
281
        }
282

283
        switch (iterator->what) {
2,640✔
284

285
        case LOOKUP_USER: {
2,040✔
286
                _cleanup_(user_group_data_done) struct user_group_data user_data = {};
2,040✔
287

288
                static const sd_json_dispatch_field dispatch_table[] = {
2,040✔
289
                        { "record",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
290
                        { "incomplete", SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool, offsetof(struct user_group_data, incomplete), 0 },
291
                        {}
292
                };
293
                _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
1,742✔
294

295
                assert_se(!iterator->found_user);
2,040✔
296

297
                r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &user_data);
2,040✔
298
                if (r < 0)
2,040✔
299
                        goto finish;
×
300

301
                if (!user_data.record) {
2,040✔
302
                        r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
×
303
                        goto finish;
×
304
                }
305

306
                hr = user_record_new();
2,040✔
307
                if (!hr) {
2,040✔
308
                        r = -ENOMEM;
×
309
                        goto finish;
×
310
                }
311

312
                r = user_record_load(hr, user_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
2,040✔
313
                if (r < 0)
2,040✔
314
                        goto finish;
×
315

316
                if (!hr->service) {
2,040✔
317
                        r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "User record does not carry service information, refusing.");
×
318
                        goto finish;
×
319
                }
320

321
                hr->incomplete = user_data.incomplete;
2,040✔
322

323
                /* We match the root user by the name since the name is our primary key. We match the nobody
324
                 * use by UID though, since the name might differ on OSes */
325
                if (streq_ptr(hr->user_name, "root"))
2,040✔
326
                        iterator->synthesize_root = false;
313✔
327
                if (hr->uid == UID_NOBODY)
2,040✔
328
                        iterator->synthesize_nobody = false;
15✔
329

330
                iterator->found_user = TAKE_PTR(hr);
2,040✔
331
                iterator->n_found++;
2,040✔
332

333
                /* More stuff coming? then let's just exit cleanly here */
334
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
2,040✔
335
                        return 0;
298✔
336

337
                /* Otherwise, let's remove this link and exit cleanly then */
338
                r = 0;
1,742✔
339
                goto finish;
1,742✔
340
        }
341

342
        case LOOKUP_GROUP: {
565✔
343
                _cleanup_(user_group_data_done) struct user_group_data group_data = {};
565✔
344

345
                static const sd_json_dispatch_field dispatch_table[] = {
565✔
346
                        { "record",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
347
                        { "incomplete", SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool, offsetof(struct user_group_data, incomplete), 0 },
348
                        {}
349
                };
350
                _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
340✔
351

352
                assert_se(!iterator->found_group);
565✔
353

354
                r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &group_data);
565✔
355
                if (r < 0)
565✔
356
                        goto finish;
×
357

358
                if (!group_data.record) {
565✔
359
                        r = log_debug_errno(SYNTHETIC_ERRNO(EIO), "Reply is missing record key");
×
360
                        goto finish;
×
361
                }
362

363
                g = group_record_new();
565✔
364
                if (!g) {
565✔
365
                        r = -ENOMEM;
×
366
                        goto finish;
×
367
                }
368

369
                r = group_record_load(g, group_data.record, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_PERMISSIVE);
565✔
370
                if (r < 0)
565✔
371
                        goto finish;
×
372

373
                if (!g->service) {
565✔
374
                        r = log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Group record does not carry service information, refusing.");
×
375
                        goto finish;
×
376
                }
377

378
                g->incomplete = group_data.incomplete;
565✔
379

380
                if (streq_ptr(g->group_name, "root"))
565✔
381
                        iterator->synthesize_root = false;
20✔
382
                if (g->gid == GID_NOBODY)
565✔
383
                        iterator->synthesize_nobody = false;
11✔
384

385
                iterator->found_group = TAKE_PTR(g);
565✔
386
                iterator->n_found++;
565✔
387

388
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
565✔
389
                        return 0;
225✔
390

391
                r = 0;
340✔
392
                goto finish;
340✔
393
        }
394

395
        case LOOKUP_MEMBERSHIP: {
35✔
396
                _cleanup_(membership_data_done) struct membership_data membership_data = {};
25✔
397

398
                static const sd_json_dispatch_field dispatch_table[] = {
35✔
399
                        { "userName",  SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, user_name),  SD_JSON_RELAX },
400
                        { "groupName", SD_JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(struct membership_data, group_name), SD_JSON_RELAX },
401
                        {}
402
                };
403

404
                assert(!iterator->found_user_name);
35✔
405
                assert(!iterator->found_group_name);
35✔
406

407
                r = sd_json_dispatch(parameters, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &membership_data);
35✔
408
                if (r < 0)
35✔
409
                        goto finish;
×
410

411
                iterator->found_user_name = TAKE_PTR(membership_data.user_name);
35✔
412
                iterator->found_group_name = TAKE_PTR(membership_data.group_name);
35✔
413
                iterator->n_found++;
35✔
414

415
                if (FLAGS_SET(flags, SD_VARLINK_REPLY_CONTINUES))
35✔
416
                        return 0;
10✔
417

418
                r = 0;
25✔
419
                goto finish;
25✔
420
        }
421

422
        default:
×
423
                assert_not_reached();
×
424
        }
425

426
finish:
2,107✔
427
        /* If we got one ESRCH or ENOEXEC, let that win. This way when we do a wild dump we won't be tripped
428
         * up by bad errors – as long as at least one connection ended somewhat cleanly */
429
        if (IN_SET(r, -ESRCH, -ENOEXEC) || iterator->error == 0)
150,612✔
430
                iterator->error = -r;
149,593✔
431

432
        assert_se(set_remove(iterator->links, link) == link);
150,612✔
433
        link = sd_varlink_unref(link);
150,612✔
434
        return 0;
150,612✔
435
}
436

437
static int userdb_connect(
151,751✔
438
                UserDBIterator *iterator,
439
                const char *path,
440
                const char *method,
441
                bool more,
442
                sd_json_variant *query) {
443

444
        _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
151,751✔
445
        int r;
151,751✔
446

447
        assert(iterator);
151,751✔
448
        assert(path);
151,751✔
449
        assert(method);
151,751✔
450

451
        r = sd_varlink_connect_address(&vl, path);
151,751✔
452
        if (r < 0)
151,751✔
453
                return log_debug_errno(r, "Unable to connect to %s: %m", path);
8✔
454

455
        sd_varlink_set_userdata(vl, iterator);
151,743✔
456

457
        if (!iterator->event) {
151,743✔
458
                r = sd_event_new(&iterator->event);
49,744✔
459
                if (r < 0)
49,744✔
460
                        return log_debug_errno(r, "Unable to allocate event loop: %m");
×
461
        }
462

463
        r = sd_varlink_attach_event(vl, iterator->event, SD_EVENT_PRIORITY_NORMAL);
151,743✔
464
        if (r < 0)
151,743✔
465
                return log_debug_errno(r, "Failed to attach varlink connection to event loop: %m");
×
466

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

474
        r = sd_varlink_bind_reply(vl, userdb_on_query_reply);
151,743✔
475
        if (r < 0)
151,743✔
476
                return log_debug_errno(r, "Failed to bind reply callback: %m");
×
477

478
        _cleanup_free_ char *service = NULL;
151,743✔
479
        r = path_extract_filename(path, &service);
151,743✔
480
        if (r < 0)
151,743✔
481
                return log_debug_errno(r, "Failed to extract service name from socket path: %m");
×
482
        assert(r != O_DIRECTORY);
151,743✔
483

484
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *patched_query = sd_json_variant_ref(query);
303,486✔
485
        r = sd_json_variant_set_field_string(&patched_query, "service", service);
151,743✔
486
        if (r < 0)
151,743✔
487
                return log_debug_errno(r, "Unable to set service JSON field: %m");
×
488

489
        if (more)
151,743✔
490
                r = sd_varlink_observe(vl, method, patched_query);
71,714✔
491
        else
492
                r = sd_varlink_invoke(vl, method, patched_query);
80,029✔
493
        if (r < 0)
151,743✔
494
                return log_debug_errno(r, "Failed to invoke varlink method: %m");
×
495

496
        r = set_ensure_consume(&iterator->links, &link_hash_ops, TAKE_PTR(vl));
151,743✔
497
        if (r < 0)
151,743✔
498
                return log_debug_errno(r, "Failed to add varlink connection to set: %m");
×
499
        return r;
500
}
501

502
static int userdb_start_query(
83,091✔
503
                UserDBIterator *iterator,
504
                const char *method, /* must be a static string, we are not going to copy this here! */
505
                bool more,
506
                sd_json_variant *query,
507
                UserDBFlags flags) {
508

509
        _cleanup_strv_free_ char **except = NULL, **only = NULL;
83,091✔
510
        _cleanup_closedir_ DIR *d = NULL;
83,091✔
511
        const char *e;
83,091✔
512
        int r, ret = 0;
83,091✔
513

514
        assert(iterator);
83,091✔
515
        assert(method);
83,091✔
516

517
        if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
83,091✔
518
                return -ENOLINK;
519

520
        assert(!iterator->query);
49,752✔
521
        iterator->method = method; /* note: we don't make a copy here! */
49,752✔
522
        iterator->query = sd_json_variant_ref(query);
49,752✔
523
        iterator->more = more;
49,752✔
524

525
        e = getenv("SYSTEMD_BYPASS_USERDB");
49,752✔
526
        if (e) {
49,752✔
527
                r = parse_boolean(e);
1,267✔
528
                if (r > 0)
1,267✔
529
                        return -ENOLINK;
530
                if (r < 0) {
1,267✔
531
                        except = strv_split(e, ":");
1,267✔
532
                        if (!except)
1,267✔
533
                                return -ENOMEM;
534
                }
535
        }
536

537
        e = getenv("SYSTEMD_ONLY_USERDB");
49,752✔
538
        if (e) {
49,752✔
539
                only = strv_split(e, ":");
×
540
                if (!only)
×
541
                        return -ENOMEM;
542
        }
543

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

556
        d = opendir("/run/systemd/userdb/");
48,158✔
557
        if (!d) {
48,158✔
558
                if (errno == ENOENT)
×
559
                        return -ESRCH;
560

561
                return -errno;
×
562
        }
563

564
        FOREACH_DIRENT(de, d, return -errno) {
392,168✔
565
                _cleanup_free_ char *p = NULL;
150,121✔
566
                bool is_nss, is_dropin;
247,694✔
567

568
                if (streq(de->d_name, "io.systemd.Multiplexer")) /* We already tried this above, don't try this again */
247,694✔
569
                        continue;
48,150✔
570

571
                if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
199,544✔
572
                    streq(de->d_name, "io.systemd.DynamicUser"))
30✔
573
                        continue;
6✔
574

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

583
                /* Similar for the drop-in service */
584
                is_dropin = streq(de->d_name, "io.systemd.DropIn");
151,420✔
585
                if ((flags & (USERDB_EXCLUDE_DROPIN|USERDB_AVOID_MULTIPLEXER)) && is_dropin)
151,420✔
586
                        continue;
1,188✔
587

588
                if (strv_contains(except, de->d_name))
150,232✔
589
                        continue;
111✔
590

591
                if (only && !strv_contains(only, de->d_name))
150,121✔
592
                        continue;
×
593

594
                p = path_join("/run/systemd/userdb/", de->d_name);
150,121✔
595
                if (!p)
150,121✔
596
                        return -ENOMEM;
×
597

598
                r = userdb_connect(iterator, p, method, more, query);
150,121✔
599
                if (is_nss && r >= 0) /* Turn off fallback NSS + dropin if we found the NSS/dropin service
150,121✔
600
                                       * and could connect to it */
601
                        iterator->nss_covered = true;
32✔
602
                if (is_dropin && r >= 0)
150,121✔
603
                        iterator->dropin_covered = true;
46,962✔
604

605
                if (ret == 0 && r < 0)
150,121✔
606
                        ret = r;
8✔
607
        }
608

609
        if (set_isempty(iterator->links))
48,158✔
610
                return ret < 0 ? ret : -ESRCH; /* propagate last error we saw if we couldn't connect to anything. */
8✔
611

612
        /* We connected to some services, in this case, ignore the ones we failed on */
613
        return 0;
614
}
615

616
static int userdb_process(
50,352✔
617
                UserDBIterator *iterator,
618
                UserRecord **ret_user_record,
619
                GroupRecord **ret_group_record,
620
                char **ret_user_name,
621
                char **ret_group_name) {
622

623
        int r;
50,352✔
624

625
        assert(iterator);
50,352✔
626

627
        for (;;) {
782,916✔
628
                if (iterator->what == LOOKUP_USER && iterator->found_user) {
782,916✔
629
                        if (ret_user_record)
2,040✔
630
                                *ret_user_record = TAKE_PTR(iterator->found_user);
2,040✔
631
                        else
632
                                iterator->found_user = user_record_unref(iterator->found_user);
×
633

634
                        if (ret_group_record)
2,040✔
635
                                *ret_group_record = NULL;
×
636
                        if (ret_user_name)
2,040✔
637
                                *ret_user_name = NULL;
×
638
                        if (ret_group_name)
2,040✔
639
                                *ret_group_name = NULL;
×
640

641
                        return 0;
2,040✔
642
                }
643

644
                if (iterator->what == LOOKUP_GROUP && iterator->found_group) {
780,876✔
645
                        if (ret_group_record)
565✔
646
                                *ret_group_record = TAKE_PTR(iterator->found_group);
565✔
647
                        else
648
                                iterator->found_group = group_record_unref(iterator->found_group);
×
649

650
                        if (ret_user_record)
565✔
651
                                *ret_user_record = NULL;
×
652
                        if (ret_user_name)
565✔
653
                                *ret_user_name = NULL;
×
654
                        if (ret_group_name)
565✔
655
                                *ret_group_name = NULL;
×
656

657
                        return 0;
565✔
658
                }
659

660
                if (iterator->what == LOOKUP_MEMBERSHIP && iterator->found_user_name && iterator->found_group_name) {
780,311✔
661
                        if (ret_user_name)
35✔
662
                                *ret_user_name = TAKE_PTR(iterator->found_user_name);
21✔
663
                        else
664
                                iterator->found_user_name = mfree(iterator->found_user_name);
14✔
665

666
                        if (ret_group_name)
35✔
667
                                *ret_group_name = TAKE_PTR(iterator->found_group_name);
28✔
668
                        else
669
                                iterator->found_group_name = mfree(iterator->found_group_name);
7✔
670

671
                        if (ret_user_record)
35✔
672
                                *ret_user_record = NULL;
×
673
                        if (ret_group_record)
35✔
674
                                *ret_group_record = NULL;
×
675

676
                        return 0;
35✔
677
                }
678

679
                if (set_isempty(iterator->links)) {
780,276✔
680
                        if (iterator->error == 0)
47,712✔
681
                                return -ESRCH;
682

683
                        return -abs(iterator->error);
47,654✔
684
                }
685

686
                if (!iterator->event)
732,564✔
687
                        return -ESRCH;
688

689
                r = sd_event_run(iterator->event, UINT64_MAX);
732,564✔
690
                if (r < 0)
732,564✔
691
                        return r;
692
        }
693
}
694

695
static int synthetic_root_user_build(UserRecord **ret) {
7✔
696
        return user_record_build(
14✔
697
                        ret,
698
                        SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("userName", JSON_BUILD_CONST_STRING("root")),
7✔
699
                                          SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(0)),
700
                                          SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(0)),
701
                                          SD_JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/root")),
702
                                          SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
703
}
704

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

716
static int synthetic_foreign_user_build(uid_t foreign_uid, UserRecord **ret) {
10✔
717
        assert(ret);
10✔
718

719
        if (!uid_is_valid(foreign_uid))
10✔
720
                return -ESRCH;
10✔
721
        if (foreign_uid > 0xFFFF)
8✔
722
                return -ESRCH;
723

724
        _cleanup_free_ char *un = NULL;
8✔
725
        if (asprintf(&un, "foreign-" UID_FMT, foreign_uid) < 0)
8✔
726
                return -ENOMEM;
727

728
        _cleanup_free_ char *rn = NULL;
8✔
729
        if (asprintf(&rn, "Foreign System Image UID " UID_FMT, foreign_uid) < 0)
8✔
730
                return -ENOMEM;
731

732
        return user_record_build(
8✔
733
                        ret,
734
                        SD_JSON_BUILD_OBJECT(
8✔
735
                                        SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(un)),
736
                                        SD_JSON_BUILD_PAIR("realName", SD_JSON_BUILD_STRING(rn)),
737
                                        SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_uid)),
738
                                        SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_uid)),
739
                                        SD_JSON_BUILD_PAIR("shell", JSON_BUILD_CONST_STRING(NOLOGIN)),
740
                                        SD_JSON_BUILD_PAIR("locked", SD_JSON_BUILD_BOOLEAN(true)),
741
                                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign"))));
742
}
743

744
static int user_name_foreign_extract_uid(const char *name, uid_t *ret_uid) {
20,428✔
745
        int r;
20,428✔
746

747
        assert(name);
20,428✔
748
        assert(ret_uid);
20,428✔
749

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

753
        const char *e = startswith(name, "foreign-");
20,428✔
754
        if (!e)
20,428✔
755
                goto nomatch;
20,421✔
756

757
        uid_t uid;
7✔
758
        r = parse_uid(e, &uid);
7✔
759
        if (r < 0)
7✔
760
                goto nomatch;
1✔
761

762
        if (uid > 0xFFFF)
6✔
763
                goto nomatch;
2✔
764

765
        *ret_uid = uid;
4✔
766
        return 1;
4✔
767

768
nomatch:
20,424✔
769
        *ret_uid = UID_INVALID;
20,424✔
770
        return 0;
20,424✔
771
}
772

773
static int query_append_disposition_mask(sd_json_variant **query, uint64_t mask) {
25✔
774
        int r;
25✔
775

776
        assert(query);
25✔
777

778
        if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL))
25✔
779
                return 0;
25✔
780

781
        _cleanup_strv_free_ char **dispositions = NULL;
17✔
782
        for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
136✔
783
                if (!BITS_SET(mask, d))
238✔
784
                        continue;
94✔
785

786
                r = strv_extend(&dispositions, user_disposition_to_string(d));
25✔
787
                if (r < 0)
25✔
788
                        return r;
789
        }
790

791
        return sd_json_variant_merge_objectbo(
17✔
792
                        query,
793
                        SD_JSON_BUILD_PAIR_STRV("dispositionMask", dispositions));
794
}
795

796
static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *match) {
4,563✔
797
        int r;
4,563✔
798

799
        assert(query);
4,563✔
800

801
        if (!userdb_match_is_set(match))
4,563✔
802
                return 0;
803

804
        r = sd_json_variant_merge_objectbo(
15✔
805
                        query,
806
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
807
                        SD_JSON_BUILD_PAIR_CONDITION(match->uid_min > 0, "uidMin", SD_JSON_BUILD_UNSIGNED(match->uid_min)),
808
                        SD_JSON_BUILD_PAIR_CONDITION(match->uid_max < UID_INVALID-1, "uidMax", SD_JSON_BUILD_UNSIGNED(match->uid_max)));
809
        if (r < 0)
13✔
810
                return r;
811

812
        return query_append_disposition_mask(query, match->disposition_mask);
13✔
813
}
814

815
static int userdb_by_name_fallbacks(
1,850✔
816
                const char *name,
817
                UserDBIterator *iterator,
818
                UserDBFlags flags,
819
                UserRecord **ret) {
820
        int r;
1,850✔
821

822
        assert(name);
1,850✔
823
        assert(iterator);
1,850✔
824
        assert(ret);
1,850✔
825

826
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
1,850✔
827
                r = dropin_user_record_by_name(name, /* path= */ NULL, flags, ret);
1,677✔
828
                if (r >= 0)
1,677✔
829
                        return r;
830
        }
831

832
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
738✔
833
                /* Make sure the NSS lookup doesn't recurse back to us. */
834

835
                r = userdb_iterator_block_nss_systemd(iterator);
209✔
836
                if (r >= 0) {
209✔
837
                        /* Client-side NSS fallback */
838
                        r = nss_user_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
209✔
839
                        if (r >= 0)
209✔
840
                                return r;
841
                }
842
        }
843

844
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
644✔
845
                if (streq(name, "root"))
274✔
846
                        return synthetic_root_user_build(ret);
1✔
847

848
                if (streq(name, NOBODY_USER_NAME) && synthesize_nobody())
273✔
849
                        return synthetic_nobody_user_build(ret);
×
850
        }
851

852
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
643✔
853
                uid_t foreign_uid;
273✔
854
                r = user_name_foreign_extract_uid(name, &foreign_uid);
273✔
855
                if (r < 0)
273✔
856
                        return r;
4✔
857
                if (r > 0)
273✔
858
                        return synthetic_foreign_user_build(foreign_uid, ret);
4✔
859
        }
860

861
        return -ESRCH;
862
}
863

864
int userdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
2,897✔
865
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
866
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
2,897✔
867
        int r;
2,897✔
868

869
        /* Well known errors this returns:
870
         *         -EINVAL    → user name is not valid
871
         *         -ESRCH     → no such user
872
         *         -ENOEXEC   → found a user by request UID or name, but it does not match filter
873
         *         -EHOSTDOWN → service failed for some reason
874
         *         -ETIMEDOUT → service timed out
875
         */
876

877
        assert(name);
2,897✔
878

879
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
2,897✔
880
                uid_t uid;
229✔
881

882
                if (parse_uid(name, &uid) >= 0)
229✔
883
                        return userdb_by_uid(uid, match, flags, ret);
156✔
884
        }
885

886
        if (!valid_user_group_name(name, VALID_USER_RELAX))
2,741✔
887
                return -EINVAL;
888

889
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
2,737✔
890
        if (r < 0)
2,737✔
891
                return r;
892

893
        r = query_append_uid_match(&query, match);
2,737✔
894
        if (r < 0)
2,737✔
895
                return r;
896

897
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
2,737✔
898
        if (!iterator)
2,737✔
899
                return -ENOMEM;
900

901
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
2,737✔
902
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
2,737✔
903
        if (r >= 0) {
2,737✔
904
                r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
1,348✔
905
                if (r == -ENOEXEC) /* found a user matching UID or name, but not filter. In this case the
1,348✔
906
                                    * fallback paths below are pointless */
907
                        return r;
908
        }
909
        if (r < 0) { /* If the above fails for any other reason, try fallback paths */
1,348✔
910
                r = userdb_by_name_fallbacks(name, iterator, flags, &ur);
1,850✔
911
                if (r < 0)
1,850✔
912
                        return r;
913
        }
914

915
        /* NB: we always apply our own filtering here, explicitly, regardless if the server supported it or
916
         * not. It's more robust this way, we never know how carefully the server is written, and whether it
917
         * properly implements all details of the filtering logic. */
918
        if (!user_record_match(ur, match))
2,098✔
919
                return -ENOEXEC;
920

921
        if (ret)
2,098✔
922
                *ret = TAKE_PTR(ur);
2,098✔
923

924
        return 0;
925
}
926

927
static int userdb_by_uid_fallbacks(
948✔
928
                uid_t uid,
929
                UserDBIterator *iterator,
930
                UserDBFlags flags,
931
                UserRecord **ret) {
932
        int r;
948✔
933

934
        assert(uid_is_valid(uid));
948✔
935
        assert(iterator);
948✔
936
        assert(ret);
948✔
937

938
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
948✔
939
                r = dropin_user_record_by_uid(uid, NULL, flags, ret);
715✔
940
                if (r >= 0)
715✔
941
                        return r;
942
        }
943

944
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
634✔
945
                r = userdb_iterator_block_nss_systemd(iterator);
188✔
946
                if (r >= 0) {
188✔
947
                        /* Client-side NSS fallback */
948
                        r = nss_user_record_by_uid(uid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
188✔
949
                        if (r >= 0)
188✔
950
                                return r;
951
                }
952
        }
953

954
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
545✔
955
                if (uid == 0)
330✔
956
                        return synthetic_root_user_build(ret);
1✔
957

958
                if (uid == UID_NOBODY && synthesize_nobody())
329✔
959
                        return synthetic_nobody_user_build(ret);
1✔
960
        }
961

962
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && uid_is_foreign(uid))
543✔
963
                return synthetic_foreign_user_build(uid - FOREIGN_UID_BASE, ret);
6✔
964

965
        return -ESRCH;
966
}
967

968
int userdb_by_uid(uid_t uid, const UserDBMatch *match, UserDBFlags flags, UserRecord **ret) {
1,784✔
969
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
970
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
1,784✔
971
        int r;
1,784✔
972

973
        if (!uid_is_valid(uid))
1,784✔
974
                return -EINVAL;
975

976
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(uid)));
1,784✔
977
        if (r < 0)
1,784✔
978
                return r;
979

980
        r = query_append_uid_match(&query, match);
1,784✔
981
        if (r < 0)
1,784✔
982
                return r;
983

984
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
1,784✔
985
        if (!iterator)
1,784✔
986
                return -ENOMEM;
987

988
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
1,784✔
989
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ false, query, flags);
1,784✔
990
        if (r >= 0) {
1,784✔
991
                r = userdb_process(iterator, &ur, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
1,368✔
992
                if (r == -ENOEXEC)
1,368✔
993
                        return r;
994
        }
995
        if (r < 0) {
1,368✔
996
                r = userdb_by_uid_fallbacks(uid, iterator, flags, &ur);
948✔
997
                if (r < 0)
948✔
998
                        return r;
999
        }
1000

1001
        if (!user_record_match(ur, match))
1,245✔
1002
                return -ENOEXEC;
1003

1004
        if (ret)
1,245✔
1005
                *ret = TAKE_PTR(ur);
1,245✔
1006

1007
        return 0;
1008
}
1009

1010
int userdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
42✔
1011
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1012
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
42✔
1013
        int r, qr;
42✔
1014

1015
        assert(ret);
42✔
1016

1017
        r = query_append_uid_match(&query, match);
42✔
1018
        if (r < 0)
42✔
1019
                return r;
1020

1021
        iterator = userdb_iterator_new(LOOKUP_USER, flags);
42✔
1022
        if (!iterator)
42✔
1023
                return -ENOMEM;
1024

1025
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetUserRecord", /* more= */ true, query, flags);
42✔
1026

1027
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
42✔
1028
                r = userdb_iterator_block_nss_systemd(iterator);
17✔
1029
                if (r < 0)
17✔
1030
                        return r;
1031

1032
                setpwent();
17✔
1033
                iterator->nss_iterating = true;
17✔
1034
        }
1035

1036
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
42✔
1037
                r = conf_files_list_nulstr(
40✔
1038
                                &iterator->dropins,
20✔
1039
                                ".user",
1040
                                NULL,
1041
                                CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1042
                                USERDB_DROPIN_DIR_NULSTR("userdb"));
1043
                if (r < 0)
20✔
1044
                        log_debug_errno(r, "Failed to find user drop-ins, ignoring: %m");
×
1045
        }
1046

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

1049
        /* propagate IPC error, but only if there are no drop-ins */
1050
        if (qr < 0 &&
42✔
1051
            !iterator->nss_iterating &&
4✔
1052
            strv_isempty(iterator->dropins))
45✔
1053
                return qr;
1054

1055
        *ret = TAKE_PTR(iterator);
42✔
1056
        return 0;
42✔
1057
}
1058

1059
static int userdb_iterator_get_one(UserDBIterator *iterator, UserRecord **ret) {
814✔
1060
        int r;
814✔
1061

1062
        assert(iterator);
814✔
1063
        assert(iterator->what == LOOKUP_USER);
814✔
1064

1065
        if (iterator->nss_iterating) {
814✔
1066
                struct passwd *pw;
425✔
1067

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

1071
                errno = 0;
425✔
1072
                pw = getpwent();
425✔
1073
                if (pw) {
425✔
1074
                        _cleanup_free_ char *buffer = NULL;
408✔
1075
                        bool incomplete = false;
408✔
1076
                        struct spwd spwd;
408✔
1077

1078
                        if (streq_ptr(pw->pw_name, "root"))
408✔
1079
                                iterator->synthesize_root = false;
17✔
1080
                        if (pw->pw_uid == UID_NOBODY)
408✔
1081
                                iterator->synthesize_nobody = false;
17✔
1082

1083
                        if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
408✔
1084
                                r = nss_spwd_for_passwd(pw, &spwd, &buffer);
408✔
1085
                                if (r < 0) {
408✔
1086
                                        log_debug_errno(r, "Failed to acquire shadow entry for user %s, ignoring: %m", pw->pw_name);
×
1087
                                        incomplete = ERRNO_IS_PRIVILEGE(r);
×
1088
                                }
1089
                        } else {
1090
                                r = -EUCLEAN;
1091
                                incomplete = true;
1092
                        }
1093

1094
                        r = nss_passwd_to_user_record(pw, r >= 0 ? &spwd : NULL, ret);
408✔
1095
                        if (r < 0)
408✔
1096
                                return r;
1097

1098
                        if (ret)
408✔
1099
                                (*ret)->incomplete = incomplete;
408✔
1100

1101
                        iterator->n_found++;
408✔
1102
                        return r;
408✔
1103
                }
1104

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

1108
                iterator->nss_iterating = false;
17✔
1109
                endpwent();
17✔
1110
        }
1111

1112
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
444✔
1113
                const char *i = iterator->dropins[iterator->current_dropin];
76✔
1114
                _cleanup_free_ char *fn = NULL;
76✔
1115
                uid_t uid;
76✔
1116
                char *e;
76✔
1117

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

1120
                r = path_extract_filename(i, &fn);
76✔
1121
                if (r < 0)
76✔
1122
                        return r;
1123

1124
                e = endswith(fn, ".user"); /* not actually a .user file? Then skip to next */
76✔
1125
                if (!e)
76✔
1126
                        continue;
×
1127

1128
                *e = 0; /* Chop off suffix */
76✔
1129

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

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

1141
                iterator->current_dropin++; /* make sure on the next call of userdb_iterator_get() we continue with the next dropin */
38✔
1142
                iterator->n_found++;
38✔
1143
                return 0;
38✔
1144
        }
1145

1146
        /* Then, let's return the users provided by varlink IPC */
1147
        r = userdb_process(iterator, ret, /* ret_group_record= */ NULL, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
368✔
1148
        if (r < 0) {
368✔
1149

1150
                /* Finally, synthesize root + nobody if not done yet */
1151
                if (iterator->synthesize_root) {
51✔
1152
                        iterator->synthesize_root = false;
5✔
1153
                        iterator->n_found++;
5✔
1154
                        return synthetic_root_user_build(ret);
5✔
1155
                }
1156

1157
                if (iterator->synthesize_nobody) {
46✔
1158
                        iterator->synthesize_nobody = false;
5✔
1159
                        iterator->n_found++;
5✔
1160
                        return synthetic_nobody_user_build(ret);
5✔
1161
                }
1162

1163
                /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1164
                if (iterator->n_found > 0)
41✔
1165
                        return -ESRCH;
41✔
1166
        }
1167

1168
        return r;
1169
}
1170

1171
int userdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, UserRecord **ret) {
700✔
1172
        int r;
700✔
1173

1174
        assert(iterator);
700✔
1175
        assert(iterator->what == LOOKUP_USER);
700✔
1176

1177
        for (;;) {
114✔
1178
                _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
700✔
1179

1180
                r = userdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &ur : NULL);
814✔
1181
                if (r < 0)
814✔
1182
                        return r;
1183

1184
                if (ur && !user_record_match(ur, match))
773✔
1185
                        continue;
114✔
1186

1187
                if (ret)
659✔
1188
                        *ret = TAKE_PTR(ur);
658✔
1189

1190
                return r;
1191
        }
1192
}
1193

1194
static int synthetic_root_group_build(GroupRecord **ret) {
7✔
1195
        return group_record_build(
14✔
1196
                        ret,
1197
                        SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING("root")),
7✔
1198
                                          SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(0)),
1199
                                          SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
1200
}
1201

1202
static int synthetic_nobody_group_build(GroupRecord **ret) {
6✔
1203
        return group_record_build(
12✔
1204
                        ret,
1205
                        SD_JSON_BUILD_OBJECT(SD_JSON_BUILD_PAIR("groupName", JSON_BUILD_CONST_STRING(NOBODY_GROUP_NAME)),
6✔
1206
                                          SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(GID_NOBODY)),
1207
                                          SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("intrinsic"))));
1208
}
1209

1210
static int synthetic_foreign_group_build(gid_t foreign_gid, GroupRecord **ret) {
7✔
1211
        assert(ret);
7✔
1212

1213
        if (!gid_is_valid(foreign_gid))
7✔
1214
                return -ESRCH;
7✔
1215
        if (foreign_gid > 0xFFFF)
7✔
1216
                return -ESRCH;
1217

1218
        _cleanup_free_ char *gn = NULL;
7✔
1219
        if (asprintf(&gn, "foreign-" GID_FMT, foreign_gid) < 0)
7✔
1220
                return -ENOMEM;
1221

1222
        _cleanup_free_ char *d = NULL;
7✔
1223
        if (asprintf(&d, "Foreign System Image GID " GID_FMT, foreign_gid) < 0)
7✔
1224
                return -ENOMEM;
1225

1226
        return group_record_build(
7✔
1227
                        ret,
1228
                        SD_JSON_BUILD_OBJECT(
7✔
1229
                                        SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(gn)),
1230
                                        SD_JSON_BUILD_PAIR("description", SD_JSON_BUILD_STRING(d)),
1231
                                        SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(FOREIGN_UID_BASE + foreign_gid)),
1232
                                        SD_JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("foreign"))));
1233
}
1234

1235
static int query_append_gid_match(sd_json_variant **query, const UserDBMatch *match) {
38,972✔
1236
        int r;
38,972✔
1237

1238
        assert(query);
38,972✔
1239

1240
        if (!userdb_match_is_set(match))
38,972✔
1241
                return 0;
1242

1243
        r = sd_json_variant_merge_objectbo(
14✔
1244
                        query,
1245
                        SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
1246
                        SD_JSON_BUILD_PAIR_CONDITION(match->gid_min > 0, "gidMin", SD_JSON_BUILD_UNSIGNED(match->gid_min)),
1247
                        SD_JSON_BUILD_PAIR_CONDITION(match->gid_max < GID_INVALID-1, "gidMax", SD_JSON_BUILD_UNSIGNED(match->gid_max)));
1248
        if (r < 0)
12✔
1249
                return r;
1250

1251
        return query_append_disposition_mask(query, match->disposition_mask);
12✔
1252
}
1253

1254
static int groupdb_by_name_fallbacks(
34,207✔
1255
                const char *name,
1256
                UserDBIterator *iterator,
1257
                UserDBFlags flags,
1258
                GroupRecord **ret) {
1259

1260
        int r;
34,207✔
1261

1262
        assert(name);
34,207✔
1263
        assert(iterator);
34,207✔
1264
        assert(ret);
34,207✔
1265

1266
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
34,207✔
1267
                r = dropin_group_record_by_name(name, NULL, flags, ret);
14,154✔
1268
                if (r >= 0)
14,154✔
1269
                        return r;
1270
        }
1271

1272
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !iterator->nss_covered) {
34,182✔
1273
                r = userdb_iterator_block_nss_systemd(iterator);
110✔
1274
                if (r >= 0) {
110✔
1275
                        r = nss_group_record_by_name(name, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
110✔
1276
                        if (r >= 0)
110✔
1277
                                return r;
1278
                }
1279
        }
1280

1281
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
34,177✔
1282
                if (streq(name, "root"))
20,156✔
1283
                        return synthetic_root_group_build(ret);
1✔
1284

1285
                if (streq(name, NOBODY_GROUP_NAME) && synthesize_nobody())
20,155✔
1286
                        return synthetic_nobody_group_build(ret);
×
1287
        }
1288

1289
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN)) {
34,176✔
1290
                uid_t foreign_gid;
20,155✔
1291
                r = user_name_foreign_extract_uid(name, &foreign_gid); /* Same for UID + GID */
20,155✔
1292
                if (r < 0)
20,155✔
1293
                        return r;
×
1294
                if (r > 0)
20,155✔
1295
                        return synthetic_foreign_group_build(foreign_gid, ret);
×
1296
        }
1297

1298
        return -ESRCH;
1299
}
1300

1301
int groupdb_by_name(const char *name, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
34,250✔
1302
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1303
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
34,250✔
1304
        int r;
34,250✔
1305

1306
        assert(name);
34,250✔
1307

1308
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
34,250✔
1309
                gid_t gid;
33✔
1310

1311
                if (parse_gid(name, &gid) >= 0)
33✔
1312
                        return groupdb_by_gid(gid, match, flags, ret);
9✔
1313
        }
1314

1315
        if (!valid_user_group_name(name, VALID_USER_RELAX))
34,241✔
1316
                return -EINVAL;
1317

1318
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
34,239✔
1319
        if (r < 0)
34,239✔
1320
                return r;
1321

1322
        r = query_append_gid_match(&query, match);
34,239✔
1323
        if (r < 0)
34,239✔
1324
                return r;
1325

1326
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
34,239✔
1327
        if (!iterator)
34,239✔
1328
                return -ENOMEM;
1329

1330
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
34,239✔
1331
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
34,239✔
1332
        if (r >= 0) {
34,239✔
1333
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
20,212✔
1334
                if (r == -ENOEXEC)
20,212✔
1335
                        return r;
1336
        }
1337
        if (r < 0) {
20,212✔
1338
                r = groupdb_by_name_fallbacks(name, iterator, flags, &gr);
34,207✔
1339
                if (r < 0)
34,207✔
1340
                        return r;
1341
        }
1342

1343
        /* As above, we apply our own client-side filtering even if server-side filtering worked, for robustness and simplicity reasons. */
1344
        if (!group_record_match(gr, match))
63✔
1345
                return -ENOEXEC;
1346

1347
        if (ret)
63✔
1348
                *ret = TAKE_PTR(gr);
63✔
1349

1350
        return 0;
1351
}
1352

1353
static int groupdb_by_gid_fallbacks(
4,414✔
1354
                gid_t gid,
1355
                UserDBIterator *iterator,
1356
                UserDBFlags flags,
1357
                GroupRecord **ret) {
1358
        int r;
4,414✔
1359

1360
        assert(gid_is_valid(gid));
4,414✔
1361
        assert(iterator);
4,414✔
1362
        assert(ret);
4,414✔
1363

1364
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !(iterator && iterator->dropin_covered)) {
4,414✔
1365
                r = dropin_group_record_by_gid(gid, NULL, flags, ret);
1,328✔
1366
                if (r >= 0)
1,328✔
1367
                        return r;
1368
        }
1369

1370
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && !(iterator && iterator->nss_covered)) {
4,378✔
1371
                r = userdb_iterator_block_nss_systemd(iterator);
150✔
1372
                if (r >= 0) {
150✔
1373
                        r = nss_group_record_by_gid(gid, !FLAGS_SET(flags, USERDB_SUPPRESS_SHADOW), ret);
150✔
1374
                        if (r >= 0)
150✔
1375
                                return r;
1376
                }
1377
        }
1378

1379
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
4,339✔
1380
                if (gid == 0)
3,186✔
1381
                        return synthetic_root_group_build(ret);
1✔
1382

1383
                if (gid == GID_NOBODY && synthesize_nobody())
3,185✔
1384
                        return synthetic_nobody_group_build(ret);
1✔
1385
        }
1386

1387
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_FOREIGN) && gid_is_foreign(gid))
4,337✔
1388
                return synthetic_foreign_group_build(gid - FOREIGN_UID_BASE, ret);
7✔
1389

1390
        return -ESRCH;
1391
}
1392

1393
int groupdb_by_gid(gid_t gid, const UserDBMatch *match, UserDBFlags flags, GroupRecord **ret) {
4,715✔
1394
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1395
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
4,715✔
1396
        int r;
4,715✔
1397

1398
        if (!gid_is_valid(gid))
4,715✔
1399
                return -EINVAL;
1400

1401
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(gid)));
4,715✔
1402
        if (r < 0)
4,715✔
1403
                return r;
1404

1405
        r = query_append_gid_match(&query, match);
4,715✔
1406
        if (r < 0)
4,715✔
1407
                return r;
1408

1409
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
4,715✔
1410
        if (!iterator)
4,715✔
1411
                return -ENOMEM;
1412

1413
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
4,715✔
1414
        r = userdb_start_query(iterator, "io.systemd.UserDatabase.GetGroupRecord", /* more= */ false, query, flags);
4,715✔
1415
        if (r >= 0) {
4,715✔
1416
                r = userdb_process(iterator, /* ret_user_record= */ NULL, &gr, /* ret_user_name= */ NULL, /* ret_group_name= */ NULL);
3,526✔
1417
                if (r == -ENOEXEC)
3,526✔
1418
                        return r;
1419
        }
1420
        if (r < 0) {
3,526✔
1421
                r = groupdb_by_gid_fallbacks(gid, iterator, flags, &gr);
4,414✔
1422
                if (r < 0)
4,414✔
1423
                        return r;
1424
        }
1425

1426
        if (!group_record_match(gr, match))
385✔
1427
                return -ENOEXEC;
1428

1429
        if (ret)
385✔
1430
                *ret = TAKE_PTR(gr);
385✔
1431

1432
        return 0;
1433
}
1434

1435
int groupdb_all(const UserDBMatch *match, UserDBFlags flags, UserDBIterator **ret) {
18✔
1436
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1437
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
18✔
1438
        int r, qr;
18✔
1439

1440
        assert(ret);
18✔
1441

1442
        r = query_append_gid_match(&query, match);
18✔
1443
        if (r < 0)
18✔
1444
                return r;
1445

1446
        iterator = userdb_iterator_new(LOOKUP_GROUP, flags);
18✔
1447
        if (!iterator)
18✔
1448
                return -ENOMEM;
1449

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

1452
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
18✔
1453
                r = userdb_iterator_block_nss_systemd(iterator);
8✔
1454
                if (r < 0)
8✔
1455
                        return r;
1456

1457
                setgrent();
8✔
1458
                iterator->nss_iterating = true;
8✔
1459
        }
1460

1461
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered)) {
18✔
1462
                r = conf_files_list_nulstr(
18✔
1463
                                &iterator->dropins,
9✔
1464
                                ".group",
1465
                                NULL,
1466
                                CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
1467
                                USERDB_DROPIN_DIR_NULSTR("userdb"));
1468
                if (r < 0)
9✔
1469
                        log_debug_errno(r, "Failed to find group drop-ins, ignoring: %m");
×
1470
        }
1471

1472
        if (qr < 0 &&
18✔
1473
            !iterator->nss_iterating &&
1✔
1474
            strv_isempty(iterator->dropins))
19✔
1475
                return qr;
1476

1477
        *ret = TAKE_PTR(iterator);
18✔
1478
        return 0;
18✔
1479
}
1480

1481
static int groupdb_iterator_get_one(UserDBIterator *iterator, GroupRecord **ret) {
726✔
1482
        int r;
726✔
1483

1484
        assert(iterator);
726✔
1485
        assert(iterator->what == LOOKUP_GROUP);
726✔
1486

1487
        if (iterator->nss_iterating) {
726✔
1488
                struct group *gr;
456✔
1489

1490
                errno = 0;
456✔
1491
                gr = getgrent();
456✔
1492
                if (gr) {
456✔
1493
                        _cleanup_free_ char *buffer = NULL;
448✔
1494
                        bool incomplete = false;
448✔
1495
                        struct sgrp sgrp;
448✔
1496

1497
                        if (streq_ptr(gr->gr_name, "root"))
448✔
1498
                                iterator->synthesize_root = false;
8✔
1499
                        if (gr->gr_gid == GID_NOBODY)
448✔
1500
                                iterator->synthesize_nobody = false;
8✔
1501

1502
                        if (!FLAGS_SET(iterator->flags, USERDB_SUPPRESS_SHADOW)) {
448✔
1503
                                r = nss_sgrp_for_group(gr, &sgrp, &buffer);
448✔
1504
                                if (r < 0) {
448✔
1505
                                        log_debug_errno(r, "Failed to acquire shadow entry for group %s, ignoring: %m", gr->gr_name);
×
1506
                                        incomplete = ERRNO_IS_PRIVILEGE(r);
×
1507
                                }
1508
                        } else {
1509
                                r = -EUCLEAN;
1510
                                incomplete = true;
1511
                        }
1512

1513
                        r = nss_group_to_group_record(gr, r >= 0 ? &sgrp : NULL, ret);
448✔
1514
                        if (r < 0)
448✔
1515
                                return r;
1516

1517
                        if (ret)
448✔
1518
                                (*ret)->incomplete = incomplete;
448✔
1519

1520
                        iterator->n_found++;
448✔
1521
                        return r;
448✔
1522
                }
1523

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

1527
                iterator->nss_iterating = false;
8✔
1528
                endgrent();
8✔
1529
        }
1530

1531
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
296✔
1532
                const char *i = iterator->dropins[iterator->current_dropin];
36✔
1533
                _cleanup_free_ char *fn = NULL;
36✔
1534
                gid_t gid;
36✔
1535
                char *e;
36✔
1536

1537
                r = path_extract_filename(i, &fn);
36✔
1538
                if (r < 0)
36✔
1539
                        return r;
1540

1541
                e = endswith(fn, ".group");
36✔
1542
                if (!e)
36✔
1543
                        continue;
×
1544

1545
                *e = 0; /* Chop off suffix */
36✔
1546

1547
                if (parse_gid(fn, &gid) < 0)
36✔
1548
                        continue;
18✔
1549

1550
                r = dropin_group_record_by_gid(gid, i, iterator->flags, ret);
18✔
1551
                if (r < 0) {
18✔
1552
                        log_debug_errno(r, "Failed to parse group record for GID " GID_FMT ", ignoring: %m", gid);
×
1553
                        continue;
×
1554
                }
1555

1556
                iterator->current_dropin++;
18✔
1557
                iterator->n_found++;
18✔
1558
                return 0;
18✔
1559
        }
1560

1561
        r = userdb_process(iterator, NULL, ret, NULL, NULL);
260✔
1562
        if (r < 0) {
260✔
1563
                if (iterator->synthesize_root) {
28✔
1564
                        iterator->synthesize_root = false;
5✔
1565
                        iterator->n_found++;
5✔
1566
                        return synthetic_root_group_build(ret);
5✔
1567
                }
1568

1569
                if (iterator->synthesize_nobody) {
23✔
1570
                        iterator->synthesize_nobody = false;
5✔
1571
                        iterator->n_found++;
5✔
1572
                        return synthetic_nobody_group_build(ret);
5✔
1573
                }
1574

1575
                /* if we found at least one entry, then ignore errors and indicate that we reached the end */
1576
                if (iterator->n_found > 0)
18✔
1577
                        return -ESRCH;
18✔
1578
        }
1579

1580
        return r;
1581
}
1582

1583
int groupdb_iterator_get(UserDBIterator *iterator, const UserDBMatch *match, GroupRecord **ret) {
484✔
1584
        int r;
484✔
1585

1586
        assert(iterator);
484✔
1587
        assert(iterator->what == LOOKUP_GROUP);
484✔
1588

1589
        for (;;) {
242✔
1590
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
484✔
1591

1592
                r = groupdb_iterator_get_one(iterator, userdb_match_is_set(match) || ret ? &gr : NULL);
726✔
1593
                if (r < 0)
726✔
1594
                        return r;
1595

1596
                if (gr && !group_record_match(gr, match))
708✔
1597
                        continue;
242✔
1598

1599
                if (ret)
466✔
1600
                        *ret = TAKE_PTR(gr);
466✔
1601

1602
                return r;
1603
        }
1604
}
1605

1606
static void discover_membership_dropins(UserDBIterator *i, UserDBFlags flags) {
16,470✔
1607
        int r;
16,470✔
1608

1609
        r = conf_files_list_nulstr(
16,470✔
1610
                        &i->dropins,
1611
                        ".membership",
1612
                        NULL,
1613
                        CONF_FILES_REGULAR|CONF_FILES_BASENAME|CONF_FILES_FILTER_MASKED,
1614
                        USERDB_DROPIN_DIR_NULSTR("userdb"));
1615
        if (r < 0)
16,470✔
1616
                log_debug_errno(r, "Failed to find membership drop-ins, ignoring: %m");
×
1617
}
16,470✔
1618

1619
int membershipdb_by_user(const char *name, UserDBFlags flags, UserDBIterator **ret) {
1,531✔
1620
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1621
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
1,531✔
1622
        int r, qr;
1,531✔
1623

1624
        assert(ret);
1,531✔
1625

1626
        if (!valid_user_group_name(name, VALID_USER_RELAX))
1,531✔
1627
                return -EINVAL;
1628

1629
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)));
1,529✔
1630
        if (r < 0)
1,529✔
1631
                return r;
1632

1633
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
1,529✔
1634
        if (!iterator)
1,529✔
1635
                return -ENOMEM;
1636

1637
        iterator->filter_user_name = strdup(name);
1,529✔
1638
        if (!iterator->filter_user_name)
1,529✔
1639
                return -ENOMEM;
1640

1641
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
1,529✔
1642

1643
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
1,529✔
1644
                r = userdb_iterator_block_nss_systemd(iterator);
127✔
1645
                if (r < 0)
127✔
1646
                        return r;
1647

1648
                setgrent();
127✔
1649
                iterator->nss_iterating = true;
127✔
1650
        }
1651

1652
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
1,529✔
1653
                discover_membership_dropins(iterator, flags);
1,355✔
1654

1655
        if (qr < 0 &&
1,529✔
1656
            !iterator->nss_iterating &&
1,228✔
1657
            strv_isempty(iterator->dropins))
2,759✔
1658
                return qr;
1659

1660
        *ret = TAKE_PTR(iterator);
301✔
1661
        return 0;
301✔
1662
}
1663

1664
int membershipdb_by_group(const char *name, UserDBFlags flags, UserDBIterator **ret) {
38,025✔
1665
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1666
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *query = NULL;
38,025✔
1667
        int r, qr;
38,025✔
1668

1669
        assert(ret);
38,025✔
1670

1671
        if (!valid_user_group_name(name, VALID_USER_RELAX))
38,025✔
1672
                return -EINVAL;
1673

1674
        r = sd_json_buildo(&query, SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)));
38,023✔
1675
        if (r < 0)
38,023✔
1676
                return r;
1677

1678
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
38,023✔
1679
        if (!iterator)
38,023✔
1680
                return -ENOMEM;
1681

1682
        iterator->filter_group_name = strdup(name);
38,023✔
1683
        if (!iterator->filter_group_name)
38,023✔
1684
                return -ENOMEM;
1685

1686
        qr = userdb_start_query(iterator, "io.systemd.UserDatabase.GetMemberships", true, query, flags);
38,023✔
1687

1688
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
38,023✔
1689
                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
20✔
1690

1691
                r = userdb_iterator_block_nss_systemd(iterator);
20✔
1692
                if (r < 0)
20✔
1693
                        return r;
1694

1695
                /* We ignore all errors here, since the group might be defined by a userdb native service, and we queried them already above. */
1696
                (void) nss_group_record_by_name(name, false, &gr);
20✔
1697
                if (gr) {
20✔
1698
                        iterator->members_of_group = strv_copy(gr->members);
8✔
1699
                        if (!iterator->members_of_group)
8✔
1700
                                return -ENOMEM;
1701

1702
                        iterator->index_members_of_group = 0;
8✔
1703

1704
                        iterator->found_group_name = strdup(name);
8✔
1705
                        if (!iterator->found_group_name)
8✔
1706
                                return -ENOMEM;
1707
                }
1708
        }
1709

1710
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
38,023✔
1711
                discover_membership_dropins(iterator, flags);
15,113✔
1712

1713
        if (qr < 0 &&
38,023✔
1714
            strv_isempty(iterator->members_of_group) &&
15,093✔
1715
            strv_isempty(iterator->dropins))
53,118✔
1716
                return qr;
1717

1718
        *ret = TAKE_PTR(iterator);
22,930✔
1719
        return 0;
22,930✔
1720
}
1721

1722
int membershipdb_all(UserDBFlags flags, UserDBIterator **ret) {
4✔
1723
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
1724
        int r, qr;
4✔
1725

1726
        assert(ret);
4✔
1727

1728
        iterator = userdb_iterator_new(LOOKUP_MEMBERSHIP, flags);
4✔
1729
        if (!iterator)
4✔
1730
                return -ENOMEM;
1731

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

1734
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_NSS) && (qr < 0 || !iterator->nss_covered)) {
4✔
1735
                r = userdb_iterator_block_nss_systemd(iterator);
2✔
1736
                if (r < 0)
2✔
1737
                        return r;
1738

1739
                setgrent();
2✔
1740
                iterator->nss_iterating = true;
2✔
1741
        }
1742

1743
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
4✔
1744
                discover_membership_dropins(iterator, flags);
2✔
1745

1746
        if (qr < 0 &&
4✔
1747
            !iterator->nss_iterating &&
×
1748
            strv_isempty(iterator->dropins))
4✔
1749
                return qr;
1750

1751
        *ret = TAKE_PTR(iterator);
4✔
1752
        return 0;
4✔
1753
}
1754

1755
int membershipdb_iterator_get(
23,302✔
1756
                UserDBIterator *iterator,
1757
                char **ret_user,
1758
                char **ret_group) {
1759

1760
        int r;
23,302✔
1761

1762
        assert(iterator);
23,302✔
1763

1764
        for (;;) {
23,368✔
1765
                /* If we are iterating through NSS acquire a new group entry if we haven't acquired one yet. */
1766
                if (!iterator->members_of_group) {
23,335✔
1767
                        struct group *g;
23,295✔
1768

1769
                        if (!iterator->nss_iterating)
23,295✔
1770
                                break;
1771

1772
                        assert(!iterator->found_user_name);
154✔
1773
                        do {
7,381✔
1774
                                errno = 0;
7,381✔
1775
                                g = getgrent();
7,381✔
1776
                                if (!g) {
7,381✔
1777
                                        if (errno != 0)
129✔
1778
                                                log_debug_errno(errno, "Failure during NSS group iteration, ignoring: %m");
×
1779
                                        break;
1780
                                }
1781

1782
                        } while (iterator->filter_user_name ? !strv_contains(g->gr_mem, iterator->filter_user_name) :
7,364✔
1783
                                                              strv_isempty(g->gr_mem));
224✔
1784

1785
                        if (g) {
154✔
1786
                                r = free_and_strdup(&iterator->found_group_name, g->gr_name);
25✔
1787
                                if (r < 0)
25✔
1788
                                        return r;
1789

1790
                                if (iterator->filter_user_name)
25✔
1791
                                        iterator->members_of_group = strv_new(iterator->filter_user_name);
15✔
1792
                                else
1793
                                        iterator->members_of_group = strv_copy(g->gr_mem);
10✔
1794
                                if (!iterator->members_of_group)
25✔
1795
                                        return -ENOMEM;
1796

1797
                                iterator->index_members_of_group = 0;
25✔
1798
                        } else {
1799
                                iterator->nss_iterating = false;
129✔
1800
                                endgrent();
129✔
1801
                                break;
129✔
1802
                        }
1803
                }
1804

1805
                assert(iterator->found_group_name);
65✔
1806
                assert(iterator->members_of_group);
65✔
1807
                assert(!iterator->found_user_name);
65✔
1808

1809
                if (iterator->members_of_group[iterator->index_members_of_group]) {
65✔
1810
                        _cleanup_free_ char *cu = NULL, *cg = NULL;
32✔
1811

1812
                        if (ret_user) {
32✔
1813
                                cu = strdup(iterator->members_of_group[iterator->index_members_of_group]);
32✔
1814
                                if (!cu)
32✔
1815
                                        return -ENOMEM;
1816
                        }
1817

1818
                        if (ret_group) {
32✔
1819
                                cg = strdup(iterator->found_group_name);
32✔
1820
                                if (!cg)
32✔
1821
                                        return -ENOMEM;
1822
                        }
1823

1824
                        if (ret_user)
32✔
1825
                                *ret_user = TAKE_PTR(cu);
32✔
1826

1827
                        if (ret_group)
32✔
1828
                                *ret_group = TAKE_PTR(cg);
32✔
1829

1830
                        iterator->index_members_of_group++;
32✔
1831
                        return 0;
32✔
1832
                }
1833

1834
                iterator->members_of_group = strv_free(iterator->members_of_group);
33✔
1835
                iterator->found_group_name = mfree(iterator->found_group_name);
33✔
1836
        }
1837

1838
        for (; iterator->dropins && iterator->dropins[iterator->current_dropin]; iterator->current_dropin++) {
23,270✔
1839
                const char *i = iterator->dropins[iterator->current_dropin], *e, *c;
×
1840
                _cleanup_free_ char *un = NULL, *gn = NULL;
×
1841

1842
                e = endswith(i, ".membership");
×
1843
                if (!e)
×
1844
                        continue;
×
1845

1846
                c = memchr(i, ':', e - i);
×
1847
                if (!c)
×
1848
                        continue;
×
1849

1850
                un = strndup(i, c - i);
×
1851
                if (!un)
×
1852
                        return -ENOMEM;
1853
                if (iterator->filter_user_name) {
×
1854
                        if (!streq(un, iterator->filter_user_name))
×
1855
                                continue;
×
1856
                } else if (!valid_user_group_name(un, VALID_USER_RELAX))
×
1857
                        continue;
×
1858

1859
                c++; /* skip over ':' */
×
1860
                gn = strndup(c, e - c);
×
1861
                if (!gn)
×
1862
                        return -ENOMEM;
1863
                if (iterator->filter_group_name) {
×
1864
                        if (!streq(gn, iterator->filter_group_name))
×
1865
                                continue;
×
1866
                } else if (!valid_user_group_name(gn, VALID_USER_RELAX))
×
1867
                        continue;
×
1868

1869
                iterator->current_dropin++;
×
1870
                iterator->n_found++;
×
1871

1872
                if (ret_user)
×
1873
                        *ret_user = TAKE_PTR(un);
×
1874
                if (ret_group)
×
1875
                        *ret_group = TAKE_PTR(gn);
×
1876

1877
                return 0;
1878
        }
1879

1880
        r = userdb_process(iterator, NULL, NULL, ret_user, ret_group);
23,270✔
1881
        if (r < 0 && iterator->n_found > 0)
23,270✔
1882
                return -ESRCH;
25✔
1883

1884
        return r;
1885
}
1886

1887
int membershipdb_by_group_strv(const char *name, UserDBFlags flags, char ***ret) {
22,885✔
1888
        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
22,885✔
1889
        _cleanup_strv_free_ char **members = NULL;
22,885✔
1890
        int r;
22,885✔
1891

1892
        assert(name);
22,885✔
1893
        assert(ret);
22,885✔
1894

1895
        r = membershipdb_by_group(name, flags, &iterator);
22,885✔
1896
        if (r < 0)
22,885✔
1897
                return r;
1898

1899
        for (;;) {
22,884✔
1900
                _cleanup_free_ char *user_name = NULL;
×
1901

1902
                r = membershipdb_iterator_get(iterator, &user_name, NULL);
22,884✔
1903
                if (r == -ESRCH)
22,884✔
1904
                        break;
1905
                if (r < 0)
×
1906
                        return r;
1907

1908
                r = strv_consume(&members, TAKE_PTR(user_name));
×
1909
                if (r < 0)
×
1910
                        return r;
1911
        }
1912

1913
        strv_sort_uniq(members);
22,884✔
1914

1915
        *ret = TAKE_PTR(members);
22,884✔
1916
        return 0;
22,884✔
1917
}
1918

1919
int userdb_block_nss_systemd(int b) {
2,243✔
1920
        _cleanup_(dlclosep) void *dl = NULL;
2,243✔
1921
        int (*call)(bool b);
2,243✔
1922

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

1925
        dl = dlopen(LIBDIR "/libnss_systemd.so.2", RTLD_NOW|RTLD_NODELETE);
2,243✔
1926
        if (!dl) {
2,243✔
1927
                /* If the file isn't installed, don't complain loudly */
1928
                log_debug("Failed to dlopen(libnss_systemd.so.2), ignoring: %s", dlerror());
×
1929
                return 0;
×
1930
        }
1931

1932
        log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2");
2,243✔
1933

1934
        call = dlsym(dl, "_nss_systemd_block");
2,243✔
1935
        if (!call)
2,243✔
1936
                /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */
1937
                return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD),
×
1938
                                       "Unable to find symbol _nss_systemd_block in libnss_systemd.so.2: %s", dlerror());
1939

1940
        return call(b);
2,243✔
1941
}
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