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

systemd / systemd / 16791678039

06 Aug 2025 11:10PM UTC coverage: 72.181% (-0.04%) from 72.223%
16791678039

push

github

yuwata
logging: Improve logging messages related to NFTSet.

The 'NFTSet' directive in various units adds and removes entries in nftables
sets, it does not add or remove entire sets. The logging messages should
indicate that an entry was added or removed, not that a set was added or
removed.

2 of 6 new or added lines in 3 files covered. (33.33%)

496 existing lines in 52 files now uncovered.

302228 of 418708 relevant lines covered (72.18%)

647735.83 hits per line

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

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

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

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

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

29
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(link_hash_ops, void, trivial_hash_func, trivial_compare_func, sd_varlink, sd_varlink_unref);
1,742✔
30

31
typedef enum LookupWhat {
32
        LOOKUP_USER,
33
        LOOKUP_GROUP,
34
        LOOKUP_MEMBERSHIP,
35
        _LOOKUP_WHAT_MAX,
36
} LookupWhat;
37

38
struct UserDBIterator {
39
        LookupWhat what;
40
        UserDBFlags flags;
41
        Set *links;
42

43
        const char *method; /* Note, this is a const static string! */
44
        sd_json_variant *query;
45

46
        bool more:1;
47
        bool nss_covered:1;
48
        bool nss_iterating:1;
49
        bool dropin_covered:1;
50
        bool synthesize_root:1;
51
        bool synthesize_nobody:1;
52
        bool nss_systemd_blocked:1;
53

54
        char **dropins;
55
        size_t current_dropin;
56
        int error;
57
        unsigned n_found;
58

59
        sd_event *event;
60
        UserRecord *found_user;                   /* when .what == LOOKUP_USER */
61
        GroupRecord *found_group;                 /* when .what == LOOKUP_GROUP */
62

63
        char *found_user_name, *found_group_name; /* when .what == LOOKUP_MEMBERSHIP */
64
        char **members_of_group;
65
        size_t index_members_of_group;
66
        char *filter_user_name, *filter_group_name;
67
};
68

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

71
UserDBIterator* userdb_iterator_free(UserDBIterator *iterator) {
75,253✔
72
        if (!iterator)
75,253✔
73
                return NULL;
74

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

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

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

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

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

88
                break;
89

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

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

96
                break;
97

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

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

108
                break;
109

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

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

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

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

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

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

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

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

139
        return i;
75,099✔
140
}
141

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

145
        assert(iterator);
8,780✔
146

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

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

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

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

163
static void user_group_data_done(struct user_group_data *d) {
5,224✔
164
        sd_json_variant_unref(d->record);
5,224✔
165
}
5,224✔
166

167
struct membership_data {
168
        char *user_name;
169
        char *group_name;
170
};
171

172
static void membership_data_done(struct membership_data *d) {
35✔
173
        free(d->user_name);
35✔
174
        free(d->group_name);
35✔
175
}
35✔
176

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

183
        int r;
147,556✔
184

185
        assert(iterator);
147,556✔
186
        assert(link);
147,556✔
187
        assert(error_id);
147,556✔
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;
991,340✔
205
        STRV_FOREACH(f, fields) {
991,340✔
206
                if (!sd_varlink_error_is_invalid_parameter(error_id, parameters, *f))
866,594✔
207
                        continue;
843,784✔
208

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

212
                restart = true;
213
                break;
214
        }
215

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

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

223
        r = sd_json_variant_filter(&patched_query, (char**const) fields);
22,810✔
224
        if (r < 0)
22,810✔
225
                return r;
226

227
        /* NB: we stored the socket path in the varlink connection description when we set things up here! */
228
        r = userdb_connect(
68,430✔
229
                        iterator,
230
                        ASSERT_PTR(sd_varlink_get_description(link)),
22,810✔
231
                        iterator->method,
232
                        iterator->more,
22,810✔
233
                        patched_query);
234
        if (r < 0)
22,810✔
235
                return r;
236

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

241
static int userdb_on_query_reply(
152,815✔
242
                sd_varlink *link,
243
                sd_json_variant *parameters,
244
                const char *error_id,
245
                sd_varlink_reply_flags_t flags,
246
                void *userdata) {
247

248
        UserDBIterator *iterator = ASSERT_PTR(userdata);
152,815✔
249
        int r;
152,815✔
250

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

254
                r = userdb_maybe_restart_query(iterator, link, parameters, error_id);
147,556✔
255
                if (r < 0)
147,556✔
256
                        return r;
257
                if (r > 0) {
147,556✔
258
                        r = 0;
22,810✔
259
                        goto finish;
22,810✔
260
                }
261

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

283
                goto finish;
124,746✔
284
        }
285

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

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

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

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

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

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

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

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

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

324
                hr->incomplete = user_data.incomplete;
2,727✔
325

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

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

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

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

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

348
                static const sd_json_dispatch_field dispatch_table[] = {
2,497✔
349
                        { "record",     _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_variant, offsetof(struct user_group_data, record),     0 },
350
                        { "incomplete", SD_JSON_VARIANT_BOOLEAN,       sd_json_dispatch_stdbool, offsetof(struct user_group_data, incomplete), 0 },
351
                        {}
352
                };
353
                _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
2,219✔
354

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

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

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

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

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

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

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

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

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

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

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

407
                assert(!iterator->found_user_name);
35✔
408
                assert(!iterator->found_group_name);
35✔
409

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

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

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

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

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

429
finish:
4,652✔
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 ||
152,208✔
434
            (r == -ESRCH && iterator->error != ENOEXEC) ||
124,702✔
435
            iterator->error == 0)
27,494✔
436
                iterator->error = -r;
145,933✔
437

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

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

453
        assert(iterator);
153,958✔
454
        assert(path);
153,958✔
455
        assert(method);
153,958✔
456

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

461
        sd_varlink_set_userdata(vl, iterator);
153,950✔
462

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

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

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

495
        if (more)
153,950✔
496
                r = sd_varlink_observe(vl, method, patched_query);
45,183✔
497
        else
498
                r = sd_varlink_invoke(vl, method, patched_query);
108,767✔
499
        if (r < 0)
153,950✔
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));
153,950✔
503
        if (r < 0)
153,950✔
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(
75,099✔
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;
75,099✔
516
        _cleanup_closedir_ DIR *d = NULL;
75,099✔
517
        const char *e;
75,099✔
518
        int r, ret = 0;
75,099✔
519

520
        assert(iterator);
75,099✔
521
        assert(method);
75,099✔
522

523
        if (FLAGS_SET(flags, USERDB_EXCLUDE_VARLINK))
75,099✔
524
                return -ENOLINK;
525

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

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

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

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

567
                return -errno;
×
568
        }
569

570
        FOREACH_DIRENT(de, d, return -errno) {
327,658✔
571
                _cleanup_free_ char *p = NULL;
121,411✔
572
                bool is_nss, is_dropin;
209,410✔
573

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

577
                if (FLAGS_SET(flags, USERDB_EXCLUDE_DYNAMIC_USER) &&
170,002✔
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");
169,996✔
586
                if ((flags & (USERDB_EXCLUDE_NSS|USERDB_AVOID_MULTIPLEXER)) && is_nss)
169,996✔
587
                        continue;
39,336✔
588

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

594
                if (strv_contains(except, de->d_name))
121,515✔
595
                        continue;
104✔
596

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

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

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

611
                RET_GATHER(ret, r);
121,411✔
612
        }
613

614
        if (set_isempty(iterator->links))
39,416✔
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(
49,832✔
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;
49,832✔
629

630
        assert(iterator);
49,832✔
631

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

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

646
                        return 0;
2,727✔
647
                }
648

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

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

662
                        return 0;
2,497✔
663
                }
664

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

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

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

681
                        return 0;
35✔
682
                }
683

684
                if (set_isempty(iterator->links)) {
779,659✔
685
                        if (iterator->error == 0)
44,573✔
686
                                return -ESRCH;
687

688
                        return -abs(iterator->error);
44,510✔
689
                }
690

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

805
        assert(query);
15,686✔
806

807
        if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL))
15,686✔
808
                return 0;
15,686✔
809

810
        _cleanup_strv_free_ char **dispositions = NULL;
15,678✔
811
        for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
125,424✔
812
                if (!BITS_SET(mask, d))
219,492✔
813
                        continue;
78,403✔
814

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

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

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

828
        assert(query);
11,868✔
829

830
        if (!userdb_match_is_set(match))
11,868✔
831
                return 0;
832

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

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

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

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

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

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

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

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

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

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

890
        return -ESRCH;
891
}
892

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

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

906
        assert(name);
6,820✔
907

908
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
6,820✔
909
                uid_t uid;
3,400✔
910

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

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

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

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

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

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

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

950
        if (ret)
2,830✔
951
                *ret = TAKE_PTR(ur);
2,830✔
952

953
        return 0;
954
}
955

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

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

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

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

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

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

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

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

997
        return -ESRCH;
998
}
999

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

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

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

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

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

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

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

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

1039
        return 0;
1040
}
1041

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

1047
        assert(ret);
43✔
1048

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1200
        return r;
1201
}
1202

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

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

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

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

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

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

1222
                return r;
1223
        }
1224
}
1225

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1294
        assert(query);
36,223✔
1295

1296
        if (!userdb_match_is_set(match))
36,223✔
1297
                return 0;
1298

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

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

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

1316
        int r;
24,642✔
1317

1318
        assert(name);
24,642✔
1319
        assert(iterator);
24,642✔
1320
        assert(ret);
24,642✔
1321

1322
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && !iterator->dropin_covered) {
24,642✔
1323
                r = dropin_group_record_by_name(name, NULL, flags, ret);
12,244✔
1324
                if (r >= 0)
12,244✔
1325
                        return r;
1326
        }
1327

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

1337
        if (!FLAGS_SET(flags, USERDB_DONT_SYNTHESIZE_INTRINSIC)) {
22,859✔
1338
                if (streq(name, "root"))
12,504✔
1339
                        return synthetic_root_group_build(ret);
1✔
1340

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

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

1354
        return -ESRCH;
1355
}
1356

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

1362
        assert(name);
29,376✔
1363

1364
        if (FLAGS_SET(flags, USERDB_PARSE_NUMERIC)) {
29,376✔
1365
                gid_t gid;
4,729✔
1366

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

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

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

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

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

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

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

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

1406
        return 0;
1407
}
1408

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

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

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

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

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

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

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

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

1449
        return -ESRCH;
1450
}
1451

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

1457
        if (!gid_is_valid(gid))
9,848✔
1458
                return -EINVAL;
1459

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

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

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

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

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

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

1491
        return 0;
1492
}
1493

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

1499
        assert(ret);
20✔
1500

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1639
        return r;
1640
}
1641

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

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

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

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

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

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

1661
                return r;
1662
        }
1663
}
1664

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

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

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

1683
        assert(ret);
1,398✔
1684

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

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

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

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

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

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

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

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

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

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

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

1728
        assert(ret);
25,610✔
1729

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

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

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

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

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

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

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

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

1761
                        iterator->index_members_of_group = 0;
8✔
1762

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

1769
        if (!FLAGS_SET(flags, USERDB_EXCLUDE_DROPIN) && (qr < 0 || !iterator->dropin_covered))
25,608✔
1770
                discover_membership_dropins(iterator, flags);
11,177✔
1771

1772
        if (qr < 0 &&
25,608✔
1773
            strv_isempty(iterator->members_of_group) &&
11,157✔
1774
            strv_isempty(iterator->dropins))
25,610✔
1775
                return qr;
1776

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

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

1785
        assert(ret);
4✔
1786

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

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

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

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

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

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

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

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

1819
        int r;
14,820✔
1820

1821
        assert(iterator);
14,820✔
1822

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

1828
                        if (!iterator->nss_iterating)
14,813✔
1829
                                break;
1830

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1936
                return 0;
1937
        }
1938

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

1943
        return r;
1944
}
1945

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

1951
        assert(name);
14,406✔
1952
        assert(ret);
14,406✔
1953

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

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

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

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

1972
        strv_sort_uniq(members);
14,405✔
1973

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

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

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

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

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

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

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

© 2026 Coveralls, Inc