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

systemd / systemd / 14895667988

07 May 2025 08:57PM UTC coverage: 72.225% (-0.007%) from 72.232%
14895667988

push

github

yuwata
network: log_link_message_debug_errno() automatically append %m if necessary

Follow-up for d28746ef5.
Fixes CID#1609753.

0 of 1 new or added line in 1 file covered. (0.0%)

20297 existing lines in 338 files now uncovered.

297407 of 411780 relevant lines covered (72.22%)

695716.85 hits per line

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

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

3
#include <getopt.h>
4

5
#include "alloc-util.h"
6
#include "bitfield.h"
7
#include "build.h"
8
#include "copy.h"
9
#include "creds-util.h"
10
#include "dirent-util.h"
11
#include "errno-list.h"
12
#include "escape.h"
13
#include "fd-util.h"
14
#include "fileio.h"
15
#include "format-table.h"
16
#include "format-util.h"
17
#include "log.h"
18
#include "main-func.h"
19
#include "mkdir-label.h"
20
#include "pager.h"
21
#include "parse-argument.h"
22
#include "parse-util.h"
23
#include "pretty-print.h"
24
#include "recurse-dir.h"
25
#include "socket-util.h"
26
#include "strv.h"
27
#include "terminal-util.h"
28
#include "uid-classification.h"
29
#include "uid-range.h"
30
#include "umask-util.h"
31
#include "user-record-show.h"
32
#include "user-util.h"
33
#include "userdb.h"
34
#include "verbs.h"
35
#include "virt.h"
36

37
static enum {
38
        OUTPUT_CLASSIC,
39
        OUTPUT_TABLE,
40
        OUTPUT_FRIENDLY,
41
        OUTPUT_JSON,
42
        _OUTPUT_INVALID = -EINVAL,
43
} arg_output = _OUTPUT_INVALID;
44

45
static PagerFlags arg_pager_flags = 0;
46
static bool arg_legend = true;
47
static char** arg_services = NULL;
48
static UserDBFlags arg_userdb_flags = 0;
49
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
50
static bool arg_chain = false;
51
static uint64_t arg_disposition_mask = UINT64_MAX;
52
static uid_t arg_uid_min = 0;
53
static uid_t arg_uid_max = UID_INVALID-1;
54
static bool arg_fuzzy = false;
55
static bool arg_boundaries = true;
56
static sd_json_variant *arg_from_file = NULL;
57

58
STATIC_DESTRUCTOR_REGISTER(arg_services, strv_freep);
307✔
59
STATIC_DESTRUCTOR_REGISTER(arg_from_file, sd_json_variant_unrefp);
307✔
60

61
static const char *user_disposition_to_color(UserDisposition d) {
444✔
62
        assert(d >= 0);
444✔
63
        assert(d < _USER_DISPOSITION_MAX);
444✔
64

65
        switch (d) {
444✔
66
        case USER_INTRINSIC:
67
                return ansi_red();
28✔
68

69
        case USER_SYSTEM:
70
        case USER_DYNAMIC:
71
                return ansi_green();
392✔
72

73
        case USER_CONTAINER:
74
        case USER_FOREIGN:
75
                return ansi_cyan();
10✔
76

77
        case USER_RESERVED:
UNCOV
78
                return ansi_red();
×
79

80
        default:
81
                return NULL;
82
        }
83
}
84

85
static const char* shell_to_color(const char *shell) {
210✔
86
        return !shell || is_nologin_shell(shell) ? ansi_grey() : NULL;
210✔
87
}
88

89
static int show_user(UserRecord *ur, Table *table) {
416✔
90
        int r;
416✔
91

92
        assert(ur);
416✔
93

94
        switch (arg_output) {
416✔
95

96
        case OUTPUT_CLASSIC:
26✔
97
                if (!uid_is_valid(ur->uid))
26✔
98
                        break;
99

100
                printf("%s:x:" UID_FMT ":" GID_FMT ":%s:%s:%s\n",
52✔
101
                       ur->user_name,
102
                       ur->uid,
103
                       user_record_gid(ur),
104
                       strempty(user_record_real_name(ur)),
26✔
105
                       user_record_home_directory(ur),
106
                       user_record_shell(ur));
107

108
                break;
26✔
109

110
        case OUTPUT_JSON:
96✔
111
                sd_json_variant_dump(ur->json, arg_json_format_flags, NULL, NULL);
96✔
112
                break;
96✔
113

114
        case OUTPUT_FRIENDLY:
84✔
115
                user_record_show(ur, true);
84✔
116

117
                if (ur->incomplete) {
84✔
118
                        fflush(stdout);
×
UNCOV
119
                        log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur->user_name);
×
120
                }
121

122
                break;
123

124
        case OUTPUT_TABLE: {
210✔
125
                assert(table);
210✔
126
                UserDisposition d = user_record_disposition(ur);
210✔
127
                const char *sh = user_record_shell(ur);
210✔
128

129
                r = table_add_many(
361✔
130
                                table,
131
                                TABLE_STRING, "",
132
                                TABLE_STRING, ur->user_name,
133
                                TABLE_SET_COLOR, user_disposition_to_color(d),
134
                                TABLE_STRING, user_disposition_to_string(d),
135
                                TABLE_UID, ur->uid,
136
                                TABLE_GID, user_record_gid(ur),
137
                                TABLE_STRING, empty_to_null(ur->real_name),
138
                                TABLE_PATH, user_record_home_directory(ur),
139
                                TABLE_PATH, sh,
140
                                TABLE_SET_COLOR, shell_to_color(sh),
141
                                TABLE_INT, 0);
142
                if (r < 0)
210✔
UNCOV
143
                        return table_log_add_error(r);
×
144

145
                break;
146
        }
147

148
        default:
×
UNCOV
149
                assert_not_reached();
×
150
        }
151

152
        return 0;
153
}
154

155
static bool test_show_mapped(void) {
16✔
156
        /* Show mapped user range only in environments where user mapping is a thing. */
157
        return running_in_userns() > 0;
16✔
158
}
159

160
static const struct {
161
        uid_t first, last;
162
        const char *name;
163
        UserDisposition disposition;
164
        bool (*test)(void);
165
} uid_range_table[] = {
166
        {
167
                .first = 1,
168
                .last = SYSTEM_UID_MAX,
169
                .name = "system",
170
                .disposition = USER_SYSTEM,
171
        },
172
        {
173
                .first = DYNAMIC_UID_MIN,
174
                .last = DYNAMIC_UID_MAX,
175
                .name = "dynamic system",
176
                .disposition = USER_DYNAMIC,
177
        },
178
        {
179
                .first = CONTAINER_UID_MIN,
180
                .last = CONTAINER_UID_MAX,
181
                .name = "container",
182
                .disposition = USER_CONTAINER,
183
        },
184
        {
185
                .first = FOREIGN_UID_MIN,
186
                .last = FOREIGN_UID_MAX,
187
                .name = "foreign",
188
                .disposition = USER_FOREIGN,
189
        },
190
#if ENABLE_HOMED
191
        {
192
                .first = HOME_UID_MIN,
193
                .last = HOME_UID_MAX,
194
                .name = "systemd-homed",
195
                .disposition = USER_REGULAR,
196
        },
197
#endif
198
        {
199
                .first = MAP_UID_MIN,
200
                .last = MAP_UID_MAX,
201
                .name = "mapped",
202
                .disposition = USER_REGULAR,
203
                .test = test_show_mapped,
204
        },
205
};
206

207
static int table_add_uid_boundaries(Table *table, const UIDRange *p) {
12✔
208
        int r, n_added = 0;
12✔
209

210
        assert(table);
12✔
211

212
        FOREACH_ELEMENT(i, uid_range_table) {
84✔
213
                _cleanup_free_ char *name = NULL, *comment = NULL;
44✔
214

215
                if (!BIT_SET(arg_disposition_mask, i->disposition))
72✔
216
                        continue;
18✔
217

218
                if (!uid_range_covers(p, i->first, i->last - i->first + 1))
54✔
UNCOV
219
                        continue;
×
220

221
                if (i->test && !i->test())
54✔
222
                        continue;
10✔
223

224
                name = strjoin(glyph(GLYPH_ARROW_DOWN),
44✔
225
                               " begin ", i->name, " users ",
226
                               glyph(GLYPH_ARROW_DOWN));
227
                if (!name)
44✔
UNCOV
228
                        return log_oom();
×
229

230
                comment = strjoin("First ", i->name, " user");
44✔
231
                if (!comment)
44✔
UNCOV
232
                        return log_oom();
×
233

234
                r = table_add_many(
44✔
235
                                table,
236
                                TABLE_STRING, glyph(GLYPH_TREE_TOP),
237
                                TABLE_STRING, name,
238
                                TABLE_SET_COLOR, ansi_grey(),
239
                                TABLE_STRING, user_disposition_to_string(i->disposition),
240
                                TABLE_SET_COLOR, ansi_grey(),
241
                                TABLE_UID, i->first,
242
                                TABLE_SET_COLOR, ansi_grey(),
243
                                TABLE_EMPTY,
244
                                TABLE_STRING, comment,
245
                                TABLE_SET_COLOR, ansi_grey(),
246
                                TABLE_EMPTY,
247
                                TABLE_EMPTY,
248
                                TABLE_INT, -1); /* sort before any other entry with the same UID */
249
                if (r < 0)
44✔
UNCOV
250
                        return table_log_add_error(r);
×
251

252
                free(name);
44✔
253
                name = strjoin(glyph(GLYPH_ARROW_UP),
44✔
254
                               " end ", i->name, " users ",
255
                               glyph(GLYPH_ARROW_UP));
256
                if (!name)
44✔
UNCOV
257
                        return log_oom();
×
258

259
                free(comment);
44✔
260
                comment = strjoin("Last ", i->name, " user");
44✔
261
                if (!comment)
44✔
UNCOV
262
                        return log_oom();
×
263

264
                r = table_add_many(
44✔
265
                                table,
266
                                TABLE_STRING, glyph(GLYPH_TREE_RIGHT),
267
                                TABLE_STRING, name,
268
                                TABLE_SET_COLOR, ansi_grey(),
269
                                TABLE_STRING, user_disposition_to_string(i->disposition),
270
                                TABLE_SET_COLOR, ansi_grey(),
271
                                TABLE_UID, i->last,
272
                                TABLE_SET_COLOR, ansi_grey(),
273
                                TABLE_EMPTY,
274
                                TABLE_STRING, comment,
275
                                TABLE_SET_COLOR, ansi_grey(),
276
                                TABLE_EMPTY,
277
                                TABLE_EMPTY,
278
                                TABLE_INT, 1); /* sort after any other entry with the same UID */
279
                if (r < 0)
44✔
UNCOV
280
                        return table_log_add_error(r);
×
281

282
                n_added += 2;
44✔
283
        }
284

285
        return n_added;
286
}
287

288
static int add_unavailable_uid(Table *table, uid_t start, uid_t end) {
×
289
        _cleanup_free_ char *name = NULL;
×
UNCOV
290
        int r;
×
291

292
        assert(table);
×
UNCOV
293
        assert(start <= end);
×
294

UNCOV
295
        name = strjoin(glyph(GLYPH_ARROW_DOWN),
×
296
                       " begin unavailable users ",
297
                       glyph(GLYPH_ARROW_DOWN));
298
        if (!name)
×
UNCOV
299
                return log_oom();
×
300

UNCOV
301
        r = table_add_many(
×
302
                        table,
303
                        TABLE_STRING, glyph(GLYPH_TREE_TOP),
304
                        TABLE_STRING, name,
305
                        TABLE_SET_COLOR, ansi_grey(),
306
                        TABLE_EMPTY,
307
                        TABLE_UID, start,
308
                        TABLE_SET_COLOR, ansi_grey(),
309
                        TABLE_EMPTY,
310
                        TABLE_STRING, "First unavailable user",
311
                        TABLE_SET_COLOR, ansi_grey(),
312
                        TABLE_EMPTY,
313
                        TABLE_EMPTY,
314
                        TABLE_INT, -1); /* sort before an other entry with the same UID */
315
        if (r < 0)
×
UNCOV
316
                return table_log_add_error(r);
×
317

318
        free(name);
×
UNCOV
319
        name = strjoin(glyph(GLYPH_ARROW_UP),
×
320
                       " end unavailable users ",
321
                       glyph(GLYPH_ARROW_UP));
322
        if (!name)
×
UNCOV
323
                return log_oom();
×
324

UNCOV
325
        r = table_add_many(
×
326
                        table,
327
                        TABLE_STRING, glyph(GLYPH_TREE_RIGHT),
328
                        TABLE_STRING, name,
329
                        TABLE_SET_COLOR, ansi_grey(),
330
                        TABLE_EMPTY,
331
                        TABLE_UID, end,
332
                        TABLE_SET_COLOR, ansi_grey(),
333
                        TABLE_EMPTY,
334
                        TABLE_STRING, "Last unavailable user",
335
                        TABLE_SET_COLOR, ansi_grey(),
336
                        TABLE_EMPTY,
337
                        TABLE_EMPTY,
338
                        TABLE_INT, 1); /* sort after any other entry with the same UID */
339
        if (r < 0)
×
UNCOV
340
                return table_log_add_error(r);
×
341

342
        return 2;
343
}
344

345
static int table_add_uid_map(
20✔
346
                Table *table,
347
                const UIDRange *p,
348
                int (*add_unavailable)(Table *t, uid_t start, uid_t end)) {
349

350
        uid_t focus = 0;
20✔
351
        int n_added = 0, r;
20✔
352

353
        assert(table);
20✔
354
        assert(add_unavailable);
20✔
355

356
        if (!p)
20✔
357
                return 0;
358

359
        FOREACH_ARRAY(x, p->entries, p->n_entries) {
40✔
360
                if (focus < x->start) {
20✔
361
                        r = add_unavailable(table, focus, x->start-1);
×
UNCOV
362
                        if (r < 0)
×
363
                                return r;
364

UNCOV
365
                        n_added += r;
×
366
                }
367

368
                if (x->start > UINT32_MAX - x->nr) { /* overflow check */
20✔
369
                        focus = UINT32_MAX;
370
                        break;
371
                }
372

373
                focus = x->start + x->nr;
20✔
374
        }
375

376
        if (focus < UINT32_MAX-1) {
20✔
377
                r = add_unavailable(table, focus, UINT32_MAX-1);
×
UNCOV
378
                if (r < 0)
×
379
                        return r;
380

UNCOV
381
                n_added += r;
×
382
        }
383

384
        return n_added;
385
}
386

387
static int display_user(int argc, char *argv[], void *userdata) {
103✔
UNCOV
388
        _cleanup_(table_unrefp) Table *table = NULL;
×
389
        bool draw_separator = false;
103✔
390
        int ret = 0, r;
103✔
391

392
        if (arg_output < 0)
103✔
393
                arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
92✔
394

395
        if (arg_output == OUTPUT_TABLE) {
103✔
396
                table = table_new(" ", "name", "disposition", "uid", "gid", "realname", "home", "shell", "order");
13✔
397
                if (!table)
13✔
UNCOV
398
                        return log_oom();
×
399

400
                (void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100);
13✔
401
                (void) table_set_align_percent(table, table_get_cell(table, 0, 4), 100);
13✔
402
                table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
13✔
403
                (void) table_set_sort(table, (size_t) 3, (size_t) 8);
13✔
404
                (void) table_hide_column_from_display(table, (size_t) 8);
13✔
405
                if (!arg_boundaries)
13✔
406
                        (void) table_hide_column_from_display(table, (size_t) 0);
1✔
407
        }
408

409
        _cleanup_(userdb_match_done) UserDBMatch match = {
103✔
410
                .disposition_mask = arg_disposition_mask,
411
                .uid_min = arg_uid_min,
412
                .uid_max = arg_uid_max,
413
        };
414

415
        if (arg_from_file) {
103✔
416
                if (argc > 1)
3✔
UNCOV
417
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No argument expected when invoked with --from-file=, refusing.");
×
418

419
                _cleanup_(user_record_unrefp) UserRecord *ur = user_record_new();
6✔
420
                if (!ur)
3✔
UNCOV
421
                        return log_oom();
×
422

423
                r = user_record_load(ur, arg_from_file, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_LOG);
3✔
424
                if (r < 0)
3✔
425
                        return r;
426

427
                r = show_user(ur, table);
3✔
428
                if (r < 0)
3✔
429
                        return r;
430

431
        } else if (argc > 1 && !arg_fuzzy)
100✔
432
                STRV_FOREACH(i, argv + 1) {
175✔
433
                        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
93✔
434

435
                        r = userdb_by_name(*i, &match, arg_userdb_flags|USERDB_PARSE_NUMERIC, &ur);
93✔
436
                        if (r < 0) {
93✔
437
                                if (r == -ESRCH)
20✔
438
                                        log_error_errno(r, "User %s does not exist.", *i);
16✔
439
                                else if (r == -EHOSTDOWN)
4✔
UNCOV
440
                                        log_error_errno(r, "Selected user database service is not available for this request.");
×
441
                                else if (r == -ENOEXEC)
4✔
UNCOV
442
                                        log_error_errno(r, "User '%s' exists but does not match specified filter.", *i);
×
443
                                else
444
                                        log_error_errno(r, "Failed to find user %s: %m", *i);
4✔
445

446
                                RET_GATHER(ret, r);
20✔
447
                        } else {
448
                                if (draw_separator && arg_output == OUTPUT_FRIENDLY)
73✔
449
                                        putchar('\n');
4✔
450

451
                                r = show_user(ur, table);
73✔
452
                                if (r < 0)
73✔
UNCOV
453
                                        return r;
×
454

455
                                draw_separator = true;
456
                        }
457
                }
458
        else {
459
                if (argc > 1) {
1✔
460
                        /* If there are further arguments, they are the fuzzy match strings. */
461
                        match.fuzzy_names = strv_copy(strv_skip(argv, 1));
1✔
462
                        if (!match.fuzzy_names)
1✔
UNCOV
463
                                return log_oom();
×
464
                }
465

UNCOV
466
                _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
467
                r = userdb_all(&match, arg_userdb_flags, &iterator);
18✔
468
                if (r == -ENOLINK) /* ENOLINK → Didn't find answer without Varlink, and didn't try Varlink because was configured to off. */
18✔
UNCOV
469
                        log_debug_errno(r, "No entries found. (Didn't check via Varlink.)");
×
470
                else if (r == -ESRCH) /* ESRCH → Couldn't find any suitable entry, but we checked all sources */
18✔
471
                        log_debug_errno(r, "No entries found.");
18✔
472
                else if (r < 0)
18✔
UNCOV
473
                        return log_error_errno(r, "Failed to enumerate users: %m");
×
474
                else {
475
                        for (;;) {
340✔
476
                                _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
18✔
477

478
                                r = userdb_iterator_get(iterator, &match, &ur);
358✔
479
                                if (r == -ESRCH)
358✔
480
                                        break;
481
                                if (r == -EHOSTDOWN)
340✔
UNCOV
482
                                        return log_error_errno(r, "Selected user database service is not available for this request.");
×
483
                                if (r < 0)
340✔
UNCOV
484
                                        return log_error_errno(r, "Failed acquire next user: %m");
×
485

486
                                if (draw_separator && arg_output == OUTPUT_FRIENDLY)
340✔
487
                                        putchar('\n');
25✔
488

489
                                r = show_user(ur, table);
340✔
490
                                if (r < 0)
340✔
491
                                        return r;
492

493
                                draw_separator = true;
340✔
494
                        }
495
                }
496
        }
497

498
        if (table) {
103✔
499
                int boundary_lines = 0, uid_map_lines = 0;
13✔
500

501
                if (arg_boundaries) {
13✔
502
                        _cleanup_(uid_range_freep) UIDRange *uid_range = NULL;
12✔
503

504
                        r = uid_range_load_userns(/* path = */ NULL, UID_RANGE_USERNS_INSIDE, &uid_range);
12✔
505
                        if (r < 0)
12✔
UNCOV
506
                                log_debug_errno(r, "Failed to load /proc/self/uid_map, ignoring: %m");
×
507

508
                        boundary_lines = table_add_uid_boundaries(table, uid_range);
12✔
509
                        if (boundary_lines < 0)
12✔
510
                                return boundary_lines;
511

512
                        uid_map_lines = table_add_uid_map(table, uid_range, add_unavailable_uid);
12✔
513
                        if (uid_map_lines < 0)
12✔
514
                                return uid_map_lines;
515
                }
516

517
                if (!table_isempty(table)) {
26✔
518
                        r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
13✔
519
                        if (r < 0)
13✔
UNCOV
520
                                return table_log_print_error(r);
×
521
                }
522

523
                if (arg_legend) {
13✔
524
                        size_t k;
12✔
525

526
                        k = table_get_rows(table) - 1 - boundary_lines - uid_map_lines;
12✔
527
                        if (k > 0)
12✔
528
                                printf("\n%zu users listed.\n", k);
10✔
529
                        else
530
                                printf("No users.\n");
2✔
531
                }
532
        }
533

534
        return ret;
535
}
536

537
static int show_group(GroupRecord *gr, Table *table) {
258✔
538
        int r;
258✔
539

540
        assert(gr);
258✔
541

542
        switch (arg_output) {
258✔
543

544
        case OUTPUT_CLASSIC: {
×
UNCOV
545
                _cleanup_free_ char *m = NULL;
×
546

UNCOV
547
                if (!gid_is_valid(gr->gid))
×
548
                        break;
549

550
                m = strv_join(gr->members, ",");
×
551
                if (!m)
×
UNCOV
552
                        return log_oom();
×
553

UNCOV
554
                printf("%s:x:" GID_FMT ":%s\n",
×
555
                       gr->group_name,
556
                       gr->gid,
557
                       m);
558
                break;
559
        }
560

561
        case OUTPUT_JSON:
5✔
562
                sd_json_variant_dump(gr->json, arg_json_format_flags, NULL, NULL);
5✔
563
                break;
5✔
564

565
        case OUTPUT_FRIENDLY:
19✔
566
                group_record_show(gr, true);
19✔
567

568
                if (gr->incomplete) {
19✔
569
                        fflush(stdout);
×
UNCOV
570
                        log_warning("Warning: lacking rights to acquire privileged fields of group record of '%s', output incomplete.", gr->group_name);
×
571
                }
572

573
                break;
574

575
        case OUTPUT_TABLE: {
234✔
576
                UserDisposition d;
234✔
577

578
                assert(table);
234✔
579
                d = group_record_disposition(gr);
234✔
580

581
                r = table_add_many(
234✔
582
                                table,
583
                                TABLE_STRING, "",
584
                                TABLE_STRING, gr->group_name,
585
                                TABLE_SET_COLOR, user_disposition_to_color(d),
586
                                TABLE_STRING, user_disposition_to_string(d),
587
                                TABLE_GID, gr->gid,
588
                                TABLE_STRING, gr->description,
589
                                TABLE_INT, 0);
590
                if (r < 0)
234✔
UNCOV
591
                        return table_log_add_error(r);
×
592

593
                break;
594
        }
595

596
        default:
×
UNCOV
597
                assert_not_reached();
×
598
        }
599

600
        return 0;
601
}
602

603
static int table_add_gid_boundaries(Table *table, const UIDRange *p) {
8✔
604
        int r, n_added = 0;
8✔
605

606
        assert(table);
8✔
607

608
        FOREACH_ELEMENT(i, uid_range_table) {
56✔
609
                _cleanup_free_ char *name = NULL, *comment = NULL;
24✔
610

611
                if (!BIT_SET(arg_disposition_mask, i->disposition))
48✔
612
                        continue;
18✔
613

614
                if (!uid_range_covers(p, i->first, i->last - i->first + 1))
30✔
UNCOV
615
                        continue;
×
616

617
                if (i->test && !i->test())
30✔
618
                        continue;
6✔
619

620
                name = strjoin(glyph(GLYPH_ARROW_DOWN),
24✔
621
                               " begin ", i->name, " groups ",
622
                               glyph(GLYPH_ARROW_DOWN));
623
                if (!name)
24✔
UNCOV
624
                        return log_oom();
×
625

626
                comment = strjoin("First ", i->name, " group");
24✔
627
                if (!comment)
24✔
UNCOV
628
                        return log_oom();
×
629

630
                r = table_add_many(
24✔
631
                                table,
632
                                TABLE_STRING, glyph(GLYPH_TREE_TOP),
633
                                TABLE_STRING, name,
634
                                TABLE_SET_COLOR, ansi_grey(),
635
                                TABLE_STRING, user_disposition_to_string(i->disposition),
636
                                TABLE_SET_COLOR, ansi_grey(),
637
                                TABLE_GID, i->first,
638
                                TABLE_SET_COLOR, ansi_grey(),
639
                                TABLE_STRING, comment,
640
                                TABLE_SET_COLOR, ansi_grey(),
641
                                TABLE_INT, -1); /* sort before any other entry with the same GID */
642
                if (r < 0)
24✔
UNCOV
643
                        return table_log_add_error(r);
×
644

645
                free(name);
24✔
646
                name = strjoin(glyph(GLYPH_ARROW_UP),
24✔
647
                               " end ", i->name, " groups ",
648
                               glyph(GLYPH_ARROW_UP));
649
                if (!name)
24✔
UNCOV
650
                        return log_oom();
×
651

652
                free(comment);
24✔
653
                comment = strjoin("Last ", i->name, " group");
24✔
654
                if (!comment)
24✔
UNCOV
655
                        return log_oom();
×
656

657
                r = table_add_many(
24✔
658
                                table,
659
                                TABLE_STRING, glyph(GLYPH_TREE_RIGHT),
660
                                TABLE_STRING, name,
661
                                TABLE_SET_COLOR, ansi_grey(),
662
                                TABLE_STRING, user_disposition_to_string(i->disposition),
663
                                TABLE_SET_COLOR, ansi_grey(),
664
                                TABLE_GID, i->last,
665
                                TABLE_SET_COLOR, ansi_grey(),
666
                                TABLE_STRING, comment,
667
                                TABLE_SET_COLOR, ansi_grey(),
668
                                TABLE_INT, 1); /* sort after any other entry with the same GID */
669
                if (r < 0)
24✔
UNCOV
670
                        return table_log_add_error(r);
×
671

672
                n_added += 2;
24✔
673
        }
674

675
        return n_added;
676
}
677

678
static int add_unavailable_gid(Table *table, uid_t start, uid_t end) {
×
679
        _cleanup_free_ char *name = NULL;
×
UNCOV
680
        int r;
×
681

682
        assert(table);
×
UNCOV
683
        assert(start <= end);
×
684

UNCOV
685
        name = strjoin(glyph(GLYPH_ARROW_DOWN),
×
686
                       " begin unavailable groups ",
687
                       glyph(GLYPH_ARROW_DOWN));
688
        if (!name)
×
UNCOV
689
                return log_oom();
×
690

UNCOV
691
        r = table_add_many(
×
692
                        table,
693
                        TABLE_STRING, glyph(GLYPH_TREE_TOP),
694
                        TABLE_STRING, name,
695
                        TABLE_SET_COLOR, ansi_grey(),
696
                        TABLE_EMPTY,
697
                        TABLE_GID, start,
698
                        TABLE_SET_COLOR, ansi_grey(),
699
                        TABLE_STRING, "First unavailable group",
700
                        TABLE_SET_COLOR, ansi_grey(),
701
                        TABLE_INT, -1); /* sort before any other entry with the same GID */
702
        if (r < 0)
×
UNCOV
703
                return table_log_add_error(r);
×
704

705
        free(name);
×
UNCOV
706
        name = strjoin(glyph(GLYPH_ARROW_UP),
×
707
                       " end unavailable groups ",
708
                       glyph(GLYPH_ARROW_UP));
709
        if (!name)
×
UNCOV
710
                return log_oom();
×
711

UNCOV
712
        r = table_add_many(
×
713
                        table,
714
                        TABLE_STRING, glyph(GLYPH_TREE_RIGHT),
715
                        TABLE_STRING, name,
716
                        TABLE_SET_COLOR, ansi_grey(),
717
                        TABLE_EMPTY,
718
                        TABLE_GID, end,
719
                        TABLE_SET_COLOR, ansi_grey(),
720
                        TABLE_STRING, "Last unavailable group",
721
                        TABLE_SET_COLOR, ansi_grey(),
722
                        TABLE_INT, 1); /* sort after any other entry with the same GID */
723
        if (r < 0)
×
UNCOV
724
                return table_log_add_error(r);
×
725

726
        return 2;
727
}
728

729
static int display_group(int argc, char *argv[], void *userdata) {
35✔
UNCOV
730
        _cleanup_(table_unrefp) Table *table = NULL;
×
731
        bool draw_separator = false;
35✔
732
        int ret = 0, r;
35✔
733

734
        if (arg_output < 0)
35✔
735
                arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
40✔
736

737
        if (arg_output == OUTPUT_TABLE) {
35✔
738
                table = table_new(" ", "name", "disposition", "gid", "description", "order");
9✔
739
                if (!table)
9✔
UNCOV
740
                        return log_oom();
×
741

742
                (void) table_set_align_percent(table, table_get_cell(table, 0, 3), 100);
9✔
743
                table_set_ersatz_string(table, TABLE_ERSATZ_DASH);
9✔
744
                (void) table_set_sort(table, (size_t) 3, (size_t) 5);
9✔
745
                (void) table_hide_column_from_display(table, (size_t) 5);
9✔
746
                if (!arg_boundaries)
9✔
747
                        (void) table_hide_column_from_display(table, (size_t) 0);
1✔
748
        }
749

750
        _cleanup_(userdb_match_done) UserDBMatch match = {
35✔
751
                .disposition_mask = arg_disposition_mask,
752
                .gid_min = arg_uid_min,
753
                .gid_max = arg_uid_max,
754
        };
755

756
        if (arg_from_file) {
35✔
757
                if (argc > 1)
3✔
UNCOV
758
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No argument expected when invoked with --from-file=, refusing.");
×
759

760
                _cleanup_(group_record_unrefp) GroupRecord *gr = group_record_new();
6✔
761
                if (!gr)
3✔
UNCOV
762
                        return log_oom();
×
763

764
                r = group_record_load(gr, arg_from_file, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_LOG);
3✔
765
                if (r < 0)
3✔
766
                        return r;
767

768
                r = show_group(gr, table);
3✔
769
                if (r < 0)
3✔
770
                        return r;
771

772
        } else if (argc > 1 && !arg_fuzzy)
32✔
773
                STRV_FOREACH(i, argv + 1) {
56✔
774
                        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
33✔
775

776
                        r = groupdb_by_name(*i, &match, arg_userdb_flags|USERDB_PARSE_NUMERIC, &gr);
33✔
777
                        if (r < 0) {
33✔
778
                                if (r == -ESRCH)
12✔
779
                                        log_error_errno(r, "Group %s does not exist.", *i);
10✔
780
                                else if (r == -EHOSTDOWN)
2✔
UNCOV
781
                                        log_error_errno(r, "Selected group database service is not available for this request.");
×
782
                                else if (r == -ENOEXEC)
2✔
UNCOV
783
                                        log_error_errno(r, "Group '%s' exists but does not match specified filter.", *i);
×
784
                                else
785
                                        log_error_errno(r, "Failed to find group %s: %m", *i);
2✔
786

787
                                RET_GATHER(ret, r);
12✔
788
                        } else {
789
                                if (draw_separator && arg_output == OUTPUT_FRIENDLY)
21✔
790
                                        putchar('\n');
4✔
791

792
                                r = show_group(gr, table);
21✔
793
                                if (r < 0)
21✔
UNCOV
794
                                        return r;
×
795

796
                                draw_separator = true;
797
                        }
798
                }
799
        else {
800
                if (argc > 1) {
1✔
801
                        match.fuzzy_names = strv_copy(strv_skip(argv, 1));
1✔
802
                        if (!match.fuzzy_names)
1✔
UNCOV
803
                                return log_oom();
×
804
                }
805

UNCOV
806
                _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
807
                r = groupdb_all(&match, arg_userdb_flags, &iterator);
9✔
808
                if (r == -ENOLINK)
9✔
UNCOV
809
                        log_debug_errno(r, "No entries found. (Didn't check via Varlink.)");
×
810
                else if (r == -ESRCH)
9✔
811
                        log_debug_errno(r, "No entries found.");
9✔
812
                else if (r < 0)
9✔
UNCOV
813
                        return log_error_errno(r, "Failed to enumerate groups: %m");
×
814
                else {
815
                        for (;;) {
234✔
816
                                _cleanup_(group_record_unrefp) GroupRecord *gr = NULL;
9✔
817

818
                                r = groupdb_iterator_get(iterator, &match, &gr);
243✔
819
                                if (r == -ESRCH)
243✔
820
                                        break;
821
                                if (r == -EHOSTDOWN)
234✔
UNCOV
822
                                        return log_error_errno(r, "Selected group database service is not available for this request.");
×
823
                                if (r < 0)
234✔
UNCOV
824
                                        return log_error_errno(r, "Failed acquire next group: %m");
×
825

826
                                if (draw_separator && arg_output == OUTPUT_FRIENDLY)
234✔
UNCOV
827
                                        putchar('\n');
×
828

829
                                r = show_group(gr, table);
234✔
830
                                if (r < 0)
234✔
831
                                        return r;
832

833
                                draw_separator = true;
234✔
834
                        }
835
                }
836
        }
837

838
        if (table) {
35✔
839
                int boundary_lines = 0, gid_map_lines = 0;
9✔
840

841
                if (arg_boundaries) {
9✔
842
                        _cleanup_(uid_range_freep) UIDRange *gid_range = NULL;
8✔
843
                        r = uid_range_load_userns(/* path = */ NULL, GID_RANGE_USERNS_INSIDE, &gid_range);
8✔
844
                        if (r < 0)
8✔
UNCOV
845
                                log_debug_errno(r, "Failed to load /proc/self/gid_map, ignoring: %m");
×
846

847
                        boundary_lines = table_add_gid_boundaries(table, gid_range);
8✔
848
                        if (boundary_lines < 0)
8✔
849
                                return boundary_lines;
850

851
                        gid_map_lines = table_add_uid_map(table, gid_range, add_unavailable_gid);
8✔
852
                        if (gid_map_lines < 0)
8✔
853
                                return gid_map_lines;
854
                }
855

856
                if (!table_isempty(table)) {
18✔
857
                        r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
9✔
858
                        if (r < 0)
9✔
UNCOV
859
                                return table_log_print_error(r);
×
860
                }
861

862
                if (arg_legend) {
9✔
863
                        size_t k;
9✔
864

865
                        k = table_get_rows(table) - 1 - boundary_lines - gid_map_lines;
9✔
866
                        if (k > 0)
9✔
867
                                printf("\n%zu groups listed.\n", k);
7✔
868
                        else
869
                                printf("No groups.\n");
2✔
870
                }
871
        }
872

873
        return ret;
874
}
875

876
static int show_membership(const char *user, const char *group, Table *table) {
14✔
877
        int r;
14✔
878

879
        assert(user);
14✔
880
        assert(group);
14✔
881

882
        switch (arg_output) {
14✔
883

UNCOV
884
        case OUTPUT_CLASSIC:
×
885
                /* Strictly speaking there's no 'classic' output for this concept, but let's output it in
886
                 * similar style to the classic output for user/group info */
887

888
                printf("%s:%s\n", user, group);
×
UNCOV
889
                break;
×
890

891
        case OUTPUT_JSON: {
2✔
892
                _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
2✔
893

894
                r = sd_json_buildo(
2✔
895
                                &v,
896
                                SD_JSON_BUILD_PAIR("user", SD_JSON_BUILD_STRING(user)),
897
                                SD_JSON_BUILD_PAIR("group", SD_JSON_BUILD_STRING(group)));
898
                if (r < 0)
2✔
UNCOV
899
                        return log_error_errno(r, "Failed to build JSON object: %m");
×
900

901
                sd_json_variant_dump(v, arg_json_format_flags, NULL, NULL);
2✔
902
                break;
2✔
903
        }
904

UNCOV
905
        case OUTPUT_FRIENDLY:
×
906
                /* Hmm, this is not particularly friendly, but not sure how we could do this better */
907
                printf("%s: %s\n", group, user);
×
UNCOV
908
                break;
×
909

910
        case OUTPUT_TABLE:
12✔
911
                assert(table);
12✔
912

913
                r = table_add_many(
12✔
914
                                table,
915
                                TABLE_STRING, user,
916
                                TABLE_STRING, group);
917
                if (r < 0)
12✔
UNCOV
918
                        return table_log_add_error(r);
×
919

920
                break;
921

922
        default:
×
UNCOV
923
                assert_not_reached();
×
924
        }
925

926
        return 0;
927
}
928

929
static int display_memberships(int argc, char *argv[], void *userdata) {
14✔
930
        _cleanup_(table_unrefp) Table *table = NULL;
14✔
931
        int ret = 0, r;
14✔
932

933
        if (arg_from_file)
14✔
UNCOV
934
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--from-file= not supported when showing memberships, refusing.");
×
935

936
        if (arg_output < 0)
14✔
937
                arg_output = OUTPUT_TABLE;
12✔
938

939
        if (arg_output == OUTPUT_TABLE) {
14✔
940
                table = table_new("user", "group");
12✔
941
                if (!table)
12✔
UNCOV
942
                        return log_oom();
×
943

944
                (void) table_set_sort(table, (size_t) 0, (size_t) 1);
12✔
945
        }
946

947
        if (argc > 1)
14✔
948
                STRV_FOREACH(i, argv + 1) {
26✔
949
                        _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
4✔
950

951
                        if (streq(argv[0], "users-in-group")) {
18✔
952
                                r = membershipdb_by_group(*i, arg_userdb_flags, &iterator);
9✔
953
                                if (r < 0)
9✔
954
                                        return log_error_errno(r, "Failed to enumerate users in group: %m");
2✔
955
                        } else if (streq(argv[0], "groups-of-user")) {
9✔
956
                                r = membershipdb_by_user(*i, arg_userdb_flags, &iterator);
9✔
957
                                if (r < 0)
9✔
958
                                        return log_error_errno(r, "Failed to enumerate groups of user: %m");
2✔
959
                        } else
UNCOV
960
                                assert_not_reached();
×
961

962
                        for (;;) {
22✔
963
                                _cleanup_free_ char *user = NULL, *group = NULL;
18✔
964

965
                                r = membershipdb_iterator_get(iterator, &user, &group);
18✔
966
                                if (r == -ESRCH)
18✔
967
                                        break;
968
                                if (r == -EHOSTDOWN)
4✔
UNCOV
969
                                        return log_error_errno(r, "Selected membership database service is not available for this request.");
×
970
                                if (r < 0)
4✔
UNCOV
971
                                        return log_error_errno(r, "Failed acquire next membership: %m");
×
972

973
                                r = show_membership(user, group, table);
4✔
974
                                if (r < 0)
4✔
975
                                        return r;
976
                        }
977
                }
978
        else {
UNCOV
979
                _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL;
×
980

981
                r = membershipdb_all(arg_userdb_flags, &iterator);
2✔
982
                if (r == -ENOLINK)
2✔
UNCOV
983
                        log_debug_errno(r, "No entries found. (Didn't check via Varlink.)");
×
984
                else if (r == -ESRCH)
2✔
985
                        log_debug_errno(r, "No entries found.");
2✔
986
                else if (r < 0)
2✔
UNCOV
987
                        return log_error_errno(r, "Failed to enumerate memberships: %m");
×
988
                else {
989
                        for (;;) {
22✔
990
                                _cleanup_free_ char *user = NULL, *group = NULL;
12✔
991

992
                                r = membershipdb_iterator_get(iterator, &user, &group);
12✔
993
                                if (r == -ESRCH)
12✔
994
                                        break;
995
                                if (r == -EHOSTDOWN)
10✔
UNCOV
996
                                        return log_error_errno(r, "Selected membership database service is not available for this request.");
×
997
                                if (r < 0)
10✔
UNCOV
998
                                        return log_error_errno(r, "Failed acquire next membership: %m");
×
999

1000
                                r = show_membership(user, group, table);
10✔
1001
                                if (r < 0)
10✔
1002
                                        return r;
1003
                        }
1004
                }
1005
        }
1006

1007
        if (table) {
10✔
1008
                if (!table_isempty(table)) {
8✔
1009
                        r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend);
4✔
1010
                        if (r < 0)
4✔
UNCOV
1011
                                return table_log_print_error(r);
×
1012
                }
1013

1014
                if (arg_legend) {
8✔
1015
                        if (table_isempty(table))
16✔
1016
                                printf("No memberships.\n");
4✔
1017
                        else
1018
                                printf("\n%zu memberships listed.\n", table_get_rows(table) - 1);
4✔
1019
                }
1020
        }
1021

1022
        return ret;
1023
}
1024

1025
static int display_services(int argc, char *argv[], void *userdata) {
2✔
UNCOV
1026
        _cleanup_(table_unrefp) Table *t = NULL;
×
1027
        _cleanup_closedir_ DIR *d = NULL;
2✔
1028
        int r;
2✔
1029

1030
        if (arg_from_file)
2✔
UNCOV
1031
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--from-file= not supported when showing services, refusing.");
×
1032

1033
        d = opendir("/run/systemd/userdb/");
2✔
1034
        if (!d) {
2✔
1035
                if (errno == ENOENT) {
×
1036
                        log_info("No services.");
×
UNCOV
1037
                        return 0;
×
1038
                }
1039

UNCOV
1040
                return log_error_errno(errno, "Failed to open /run/systemd/userdb/: %m");
×
1041
        }
1042

1043
        t = table_new("service", "listening");
2✔
1044
        if (!t)
2✔
UNCOV
1045
                return log_oom();
×
1046

1047
        (void) table_set_sort(t, (size_t) 0);
2✔
1048

1049
        FOREACH_DIRENT(de, d, return -errno) {
18✔
1050
                _cleanup_free_ char *j = NULL, *no = NULL;
12✔
1051
                _cleanup_close_ int fd = -EBADF;
12✔
1052

1053
                j = path_join("/run/systemd/userdb/", de->d_name);
12✔
1054
                if (!j)
12✔
UNCOV
1055
                        return log_oom();
×
1056

1057
                fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
12✔
1058
                if (fd < 0)
12✔
UNCOV
1059
                        return log_error_errno(errno, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m");
×
1060

1061
                r = connect_unix_path(fd, dirfd(d), de->d_name);
12✔
1062
                if (r < 0) {
12✔
1063
                        no = strjoin("No (", errno_to_name(r), ")");
×
1064
                        if (!no)
×
UNCOV
1065
                                return log_oom();
×
1066
                }
1067

1068
                r = table_add_many(t,
24✔
1069
                                   TABLE_STRING, de->d_name,
1070
                                   TABLE_STRING, no ?: "yes",
1071
                                   TABLE_SET_COLOR, ansi_highlight_green_red(!no));
1072
                if (r < 0)
12✔
UNCOV
1073
                        return table_log_add_error(r);
×
1074
        }
1075

1076
        if (!table_isempty(t)) {
4✔
1077
                r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend);
2✔
1078
                if (r < 0)
2✔
UNCOV
1079
                        return table_log_print_error(r);
×
1080
        }
1081

1082
        if (arg_legend && arg_output != OUTPUT_JSON) {
2✔
1083
                if (table_isempty(t))
2✔
UNCOV
1084
                        printf("No services.\n");
×
1085
                else
1086
                        printf("\n%zu services listed.\n", table_get_rows(t) - 1);
1✔
1087
        }
1088

1089
        return 0;
1090
}
1091

1092
static int ssh_authorized_keys(int argc, char *argv[], void *userdata) {
10✔
UNCOV
1093
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
×
1094
        char **chain_invocation;
10✔
1095
        int r;
10✔
1096

1097
        assert(argc >= 2);
10✔
1098

1099
        if (arg_from_file)
10✔
UNCOV
1100
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--from-file= not supported when showing SSH authorized keys, refusing.");
×
1101

1102
        if (arg_chain) {
10✔
1103
                /* If --chain is specified, the rest of the command line is the chain command */
1104

1105
                if (argc < 3)
4✔
1106
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1✔
1107
                                               "No chain command line specified, refusing.");
1108

1109
                /* Make similar restrictions on the chain command as OpenSSH itself makes on the primary command. */
1110
                if (!path_is_absolute(argv[2]))
3✔
1111
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
1✔
1112
                                               "Chain invocation of ssh-authorized-keys commands requires an absolute binary path argument.");
1113

1114
                if (!path_is_normalized(argv[2]))
2✔
UNCOV
1115
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1116
                                               "Chain invocation of ssh-authorized-keys commands requires an normalized binary path argument.");
1117

1118
                chain_invocation = argv + 2;
2✔
1119
        } else {
1120
                /* If --chain is not specified, then refuse any further arguments */
1121

1122
                if (argc > 2)
6✔
UNCOV
1123
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");
×
1124

1125
                chain_invocation = NULL;
1126
        }
1127

1128
        r = userdb_by_name(argv[1], /* match= */ NULL, arg_userdb_flags, &ur);
8✔
1129
        if (r == -ESRCH)
8✔
1130
                log_error_errno(r, "User %s does not exist.", argv[1]);
2✔
1131
        else if (r == -EHOSTDOWN)
6✔
UNCOV
1132
                log_error_errno(r, "Selected user database service is not available for this request.");
×
1133
        else if (r == -EINVAL)
6✔
UNCOV
1134
                log_error_errno(r, "Failed to find user %s: %m (Invalid user name?)", argv[1]);
×
1135
        else if (r < 0)
6✔
UNCOV
1136
                log_error_errno(r, "Failed to find user %s: %m", argv[1]);
×
1137
        else {
1138
                if (strv_isempty(ur->ssh_authorized_keys))
6✔
UNCOV
1139
                        log_debug("User record for %s has no public SSH keys.", argv[1]);
×
1140
                else
1141
                        STRV_FOREACH(i, ur->ssh_authorized_keys)
14✔
1142
                                printf("%s\n", *i);
8✔
1143

1144
                if (ur->incomplete) {
6✔
1145
                        fflush(stdout);
×
UNCOV
1146
                        log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur->user_name);
×
1147
                }
1148
        }
1149

1150
        if (chain_invocation) {
8✔
1151
                if (DEBUG_LOGGING) {
2✔
1152
                        _cleanup_free_ char *s = NULL;
1✔
1153

1154
                        s = quote_command_line(chain_invocation, SHELL_ESCAPE_EMPTY);
1✔
1155
                        if (!s)
1✔
UNCOV
1156
                                return log_oom();
×
1157

1158
                        log_debug("Chain invoking: %s", s);
1✔
1159
                }
1160

1161
                fflush(stdout);
2✔
1162
                execv(chain_invocation[0], chain_invocation);
×
UNCOV
1163
                if (errno == ENOENT) /* Let's handle ENOENT gracefully */
×
1164
                        log_warning_errno(errno, "Chain executable '%s' does not exist, ignoring chain invocation.", chain_invocation[0]);
8✔
1165
                else {
1166
                        log_error_errno(errno, "Failed to invoke chain executable '%s': %m", chain_invocation[0]);
×
1167
                        if (r >= 0)
×
UNCOV
1168
                                r = -errno;
×
1169
                }
1170
        }
1171

1172
        return r;
1173
}
1174

1175
static int load_credential_one(int credential_dir_fd, const char *name, int userdb_dir_fd) {
228✔
1176
        int r;
228✔
1177

1178
        assert(credential_dir_fd >= 0);
228✔
1179
        assert(name);
228✔
1180
        assert(userdb_dir_fd >= 0);
228✔
1181

1182
        const char *user = startswith(name, "userdb.user.");
228✔
1183
        const char *group = startswith(name, "userdb.group.");
228✔
1184
        if (!user && !group)
228✔
1185
                return 0;
228✔
1186

1187
        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
228✔
1188
        unsigned line = 0, column = 0;
228✔
1189
        r = sd_json_parse_file_at(NULL, credential_dir_fd, name, SD_JSON_PARSE_SENSITIVE, &v, &line, &column);
228✔
1190
        if (r < 0)
228✔
UNCOV
1191
                return log_error_errno(r, "Failed to parse credential '%s' as JSON at %u:%u: %m", name, line, column);
×
1192

1193
        _cleanup_(user_record_unrefp) UserRecord *ur = NULL, *ur_stripped = NULL, *ur_privileged = NULL;
228✔
1194
        _cleanup_(group_record_unrefp) GroupRecord *gr = NULL, *gr_stripped = NULL, *gr_privileged = NULL;
228✔
1195
        _cleanup_free_ char *fn = NULL, *link = NULL;
228✔
1196

1197
        if (user) {
228✔
1198
                ur = user_record_new();
114✔
1199
                if (!ur)
114✔
1200
                        return log_oom();
14✔
1201

1202
                r = user_record_load(ur, v, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_LOG);
114✔
1203
                if (r < 0)
114✔
1204
                        return r;
1205

1206
                if (user_record_is_root(ur))
114✔
UNCOV
1207
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Creating 'root' user from credentials is not supported.");
×
1208
                if (user_record_is_nobody(ur))
114✔
UNCOV
1209
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Creating 'nobody' user from credentials is not supported.");
×
1210

1211
                if (!streq_ptr(user, ur->user_name))
114✔
UNCOV
1212
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1213
                                               "Credential suffix '%s' does not match user record name '%s'",
1214
                                               user, strna(ur->user_name));
1215

1216
                if (!uid_is_valid(ur->uid))
114✔
UNCOV
1217
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "JSON user record missing uid field");
×
1218

1219
                if (!gid_is_valid(user_record_gid(ur)))
114✔
UNCOV
1220
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "JSON user record missing gid field");
×
1221

1222
                _cleanup_(user_record_unrefp) UserRecord *m = NULL;
114✔
1223
                r = userdb_by_name(ur->user_name, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, &m);
114✔
1224
                if (r >= 0) {
114✔
1225
                        if (m->uid != ur->uid)
14✔
UNCOV
1226
                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
1227
                                                       "Cannot create user %s from credential %s as it already exists with UID " UID_FMT " instead of " UID_FMT,
1228
                                                       ur->user_name, name, m->uid, ur->uid);
1229

1230
                        log_info("User with name %s and UID " UID_FMT " already exists, not creating user from credential %s", ur->user_name, ur->uid, name);
14✔
1231
                        return 0;
14✔
1232
                }
1233
                if (r != -ESRCH)
100✔
UNCOV
1234
                        return log_error_errno(r, "Failed to check if user with name %s already exists: %m", ur->user_name);
×
1235

1236
                m = user_record_unref(m);
100✔
1237
                r = userdb_by_uid(ur->uid, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, &m);
100✔
1238
                if (r >= 0) {
100✔
1239
                        if (!streq_ptr(ur->user_name, m->user_name))
×
UNCOV
1240
                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
1241
                                                       "Cannot create user %s from credential %s as UID " UID_FMT " is already assigned to user %s",
1242
                                                       ur->user_name, name, ur->uid, m->user_name);
1243

1244
                        log_info("User with name %s and UID " UID_FMT " already exists, not creating user from credential %s", ur->user_name, ur->uid, name);
×
UNCOV
1245
                        return 0;
×
1246
                }
1247
                if (r != -ESRCH)
100✔
UNCOV
1248
                        return log_error_errno(r, "Failed to check if user with UID " UID_FMT " already exists: %m", ur->uid);
×
1249

1250
                r = user_record_clone(ur, USER_RECORD_LOAD_MASK_PRIVILEGED|USER_RECORD_LOG, &ur_stripped);
100✔
1251
                if (r < 0)
100✔
1252
                        return r;
1253

1254
                r = user_record_clone(ur, USER_RECORD_EXTRACT_PRIVILEGED|USER_RECORD_EMPTY_OK|USER_RECORD_LOG, &ur_privileged);
100✔
1255
                if (r < 0)
100✔
1256
                        return r;
1257

1258
                fn = strjoin(ur->user_name, ".user");
100✔
1259
                if (!fn)
100✔
UNCOV
1260
                        return log_oom();
×
1261

1262
                if (asprintf(&link, UID_FMT ".user", ur->uid) < 0)
100✔
UNCOV
1263
                        return log_oom();
×
1264
        } else {
1265
                assert(group);
114✔
1266

1267
                gr = group_record_new();
114✔
1268
                if (!gr)
114✔
1269
                        return log_oom();
14✔
1270

1271
                r = group_record_load(gr, v, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_LOG);
114✔
1272
                if (r < 0)
114✔
1273
                        return r;
1274

1275
                if (group_record_is_root(gr))
114✔
UNCOV
1276
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Creating 'root' group from credentials is not supported.");
×
1277
                if (group_record_is_nobody(gr))
114✔
UNCOV
1278
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Creating 'nobody' group from credentials is not supported.");
×
1279

1280
                if (!streq_ptr(group, gr->group_name))
114✔
UNCOV
1281
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1282
                                               "Credential suffix '%s' does not match group record name '%s'",
1283
                                               group, strna(gr->group_name));
1284

1285
                if (!gid_is_valid(gr->gid))
114✔
UNCOV
1286
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "JSON group record missing gid field");
×
1287

1288
                _cleanup_(group_record_unrefp) GroupRecord *m = NULL;
114✔
1289
                r = groupdb_by_name(gr->group_name, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, &m);
114✔
1290
                if (r >= 0) {
114✔
1291
                        if (m->gid != gr->gid)
14✔
UNCOV
1292
                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
1293
                                                       "Cannot create group %s from credential %s as it already exists with GID " GID_FMT " instead of " GID_FMT,
1294
                                                       gr->group_name, name, m->gid, gr->gid);
1295

1296
                        log_info("Group with name %s and GID " GID_FMT " already exists, not creating group from credential %s", gr->group_name, gr->gid, name);
14✔
1297
                        return 0;
14✔
1298
                }
1299
                if (r != -ESRCH)
100✔
UNCOV
1300
                        return log_error_errno(r, "Failed to check if group with name %s already exists: %m", gr->group_name);
×
1301

1302
                m = group_record_unref(m);
100✔
1303
                r = groupdb_by_gid(gr->gid, /* match= */ NULL, USERDB_SUPPRESS_SHADOW, &m);
100✔
1304
                if (r >= 0) {
100✔
1305
                        if (!streq_ptr(gr->group_name, m->group_name))
×
UNCOV
1306
                                return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
×
1307
                                                       "Cannot create group %s from credential %s as GID " GID_FMT " is already assigned to group %s",
1308
                                                       gr->group_name, name, gr->gid, m->group_name);
1309

1310
                        log_info("Group with name %s and GID " GID_FMT " already exists, not creating group from credential %s", gr->group_name, gr->gid, name);
×
UNCOV
1311
                        return 0;
×
1312
                }
1313
                if (r != -ESRCH)
100✔
UNCOV
1314
                        return log_error_errno(r, "Failed to check if group with GID " GID_FMT " already exists: %m", gr->gid);
×
1315

1316
                r = group_record_clone(gr, USER_RECORD_LOAD_MASK_PRIVILEGED|USER_RECORD_LOG, &gr_stripped);
100✔
1317
                if (r < 0)
100✔
1318
                        return r;
1319

1320
                r = group_record_clone(gr, USER_RECORD_EXTRACT_PRIVILEGED|USER_RECORD_EMPTY_OK|USER_RECORD_LOG, &gr_privileged);
100✔
1321
                if (r < 0)
100✔
1322
                        return r;
1323

1324
                fn = strjoin(gr->group_name, ".group");
100✔
1325
                if (!fn)
100✔
UNCOV
1326
                        return log_oom();
×
1327

1328
                if (asprintf(&link, GID_FMT ".group", gr->gid) < 0)
100✔
UNCOV
1329
                        return log_oom();
×
1330
        }
1331

1332
        if (!filename_is_valid(fn))
200✔
UNCOV
1333
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
×
1334
                                       "Passed credential '%s' would result in invalid filename '%s'.",
1335
                                       name, fn);
1336

1337
        _cleanup_free_ char *formatted = NULL;
200✔
1338
        r = sd_json_variant_format(ur ? ur_stripped->json : gr_stripped->json, SD_JSON_FORMAT_NEWLINE, &formatted);
200✔
1339
        if (r < 0)
200✔
UNCOV
1340
                return log_error_errno(r, "Failed to format JSON record: %m");
×
1341

1342
        r = write_string_file_at(userdb_dir_fd, fn, formatted, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
200✔
1343
        if (r < 0)
200✔
UNCOV
1344
                return log_error_errno(r, "Failed to write JSON record to /etc/userdb/%s: %m", fn);
×
1345

1346
        if (symlinkat(fn, userdb_dir_fd, link) < 0)
200✔
UNCOV
1347
                return log_error_errno(errno, "Failed to create symlink from %s to %s", link, fn);
×
1348

1349
        log_info("Installed /etc/userdb/%s from credential.", fn);
200✔
1350

1351
        if ((ur && !sd_json_variant_is_blank_object(ur_privileged->json)) ||
200✔
1352
            (gr && !sd_json_variant_is_blank_object(gr_privileged->json))) {
100✔
1353
                fn = mfree(fn);
100✔
1354
                fn = strjoin(ur ? ur->user_name : gr->group_name, ur ? ".user-privileged" : ".group-privileged");
100✔
1355
                if (!fn)
100✔
UNCOV
1356
                        return log_oom();
×
1357

1358
                formatted = mfree(formatted);
100✔
1359
                r = sd_json_variant_format(ur ? ur_privileged->json : gr_privileged->json, SD_JSON_FORMAT_NEWLINE, &formatted);
100✔
1360
                if (r < 0)
100✔
UNCOV
1361
                        return log_error_errno(r, "Failed to format JSON record: %m");
×
1362

1363
                r = write_string_file_at(userdb_dir_fd, fn, formatted, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MODE_0600);
100✔
1364
                if (r < 0)
100✔
UNCOV
1365
                        return log_error_errno(r, "Failed to write JSON record to /etc/userdb/%s: %m", fn);
×
1366

1367
                link = mfree(link);
100✔
1368

1369
                if (ur) {
100✔
1370
                        if (asprintf(&link, UID_FMT ".user-privileged", ur->uid) < 0)
100✔
UNCOV
1371
                                return log_oom();
×
1372
                } else {
1373
                        if (asprintf(&link, GID_FMT ".group-privileged", gr->gid) < 0)
×
UNCOV
1374
                                return log_oom();
×
1375
                }
1376

1377
                if (symlinkat(fn, userdb_dir_fd, link) < 0)
100✔
UNCOV
1378
                        return log_error_errno(errno, "Failed to create symlink from %s to %s", link, fn);
×
1379

1380
                log_info("Installed /etc/userdb/%s from credential.", fn);
100✔
1381
        }
1382

1383
        if (ur)
200✔
1384
                STRV_FOREACH(g, ur->member_of) {
300✔
1385
                        _cleanup_free_ char *membership = strjoin(ur->user_name, ":", *g);
400✔
1386
                        if (!membership)
200✔
UNCOV
1387
                                return log_oom();
×
1388

1389
                        _cleanup_close_ int fd = openat(userdb_dir_fd, membership, O_WRONLY|O_CREAT|O_CLOEXEC, 0644);
400✔
1390
                        if (fd < 0)
200✔
UNCOV
1391
                                return log_error_errno(errno, "Failed to create %s: %m", membership);
×
1392

1393
                        log_info("Installed /etc/userdb/%s from credential.", membership);
200✔
1394
                }
1395
        else
1396
                STRV_FOREACH(u, gr->members) {
100✔
1397
                        _cleanup_free_ char *membership = strjoin(*u, ":", gr->group_name);
×
1398
                        if (!membership)
×
UNCOV
1399
                                return log_oom();
×
1400

1401
                        _cleanup_close_ int fd = openat(userdb_dir_fd, membership, O_WRONLY|O_CREAT|O_CLOEXEC, 0644);
×
1402
                        if (fd < 0)
×
UNCOV
1403
                                return log_error_errno(errno, "Failed to create %s: %m", membership);
×
1404

UNCOV
1405
                        log_info("Installed /etc/userdb/%s from credential.", membership);
×
1406
                }
1407

1408
        if (ur && user_record_disposition(ur) == USER_REGULAR) {
200✔
1409
                const char *hd = user_record_home_directory(ur);
100✔
1410

1411
                r = RET_NERRNO(access(hd, F_OK));
300✔
1412
                if (r < 0) {
100✔
1413
                        if (r != -ENOENT)
100✔
UNCOV
1414
                                return log_error_errno(r, "Failed to check if %s exists: %m", hd);
×
1415

1416
                        WITH_UMASK(0000) {
200✔
1417
                                r = mkdir_parents(hd, 0755);
100✔
1418
                                if (r < 0)
100✔
UNCOV
1419
                                        return log_error_errno(r, "Failed to create parent directories of %s: %m", hd);
×
1420

1421
                                if (mkdir(hd, 0700) < 0 && errno != EEXIST)
100✔
UNCOV
1422
                                        return log_error_errno(errno, "Failed to create %s: %m", hd);
×
1423
                        }
1424

1425
                        if (chown(hd, ur->uid, user_record_gid(ur)) < 0)
100✔
UNCOV
1426
                                return log_error_errno(errno, "Failed to chown %s: %m", hd);
×
1427

1428
                        r = copy_tree(user_record_skeleton_directory(ur), hd, ur->uid, user_record_gid(ur),
100✔
1429
                                      COPY_REFLINK|COPY_MERGE, /* denylist= */ NULL, /* subvolumes= */NULL);
1430
                        if (r < 0 && r != -ENOENT)
100✔
UNCOV
1431
                                return log_error_errno(r, "Failed to copy skeleton directory to %s: %m", hd);
×
1432
                }
1433
        }
1434

1435
        return 0;
1436
}
1437

1438
static int load_credentials(int argc, char *argv[], void *userdata) {
114✔
1439
        int r;
114✔
1440

1441
        _cleanup_close_ int credential_dir_fd = open_credentials_dir();
228✔
1442
        if (IN_SET(credential_dir_fd, -ENXIO, -ENOENT)) {
114✔
1443
                /* Credential env var not set, or dir doesn't exist. */
1444
                log_debug("No credentials found.");
×
UNCOV
1445
                return 0;
×
1446
        }
1447
        if (credential_dir_fd < 0)
114✔
UNCOV
1448
                return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m");
×
1449

1450
        _cleanup_free_ DirectoryEntries *des = NULL;
114✔
1451
        r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des);
114✔
1452
        if (r < 0)
114✔
UNCOV
1453
                return log_error_errno(r, "Failed to enumerate credentials: %m");
×
1454

1455
        _cleanup_close_ int userdb_dir_fd = xopenat_full(
228✔
1456
                AT_FDCWD, "/etc/userdb",
1457
                /* open_flags= */ O_DIRECTORY|O_CREAT|O_CLOEXEC,
1458
                /* xopen_flags= */ XO_LABEL,
1459
                /* mode= */ 0755);
1460
        if (userdb_dir_fd < 0)
114✔
UNCOV
1461
                return log_error_errno(userdb_dir_fd, "Failed to open '/etc/userdb/': %m");
×
1462

1463
        FOREACH_ARRAY(i, des->entries, des->n_entries) {
342✔
1464
                struct dirent *de = *i;
228✔
1465

1466
                if (de->d_type != DT_REG)
228✔
UNCOV
1467
                        continue;
×
1468

1469
                RET_GATHER(r, load_credential_one(credential_dir_fd, de->d_name, userdb_dir_fd));
228✔
1470
        }
1471

1472
        return r;
1473
}
1474

1475
static int help(int argc, char *argv[], void *userdata) {
1✔
1476
        _cleanup_free_ char *link = NULL;
1✔
1477
        int r;
1✔
1478

1479
        pager_open(arg_pager_flags);
1✔
1480

1481
        r = terminal_urlify_man("userdbctl", "1", &link);
1✔
1482
        if (r < 0)
1✔
UNCOV
1483
                return log_oom();
×
1484

1485
        printf("%s [OPTIONS...] COMMAND ...\n\n"
2✔
1486
               "%sShow user and group information.%s\n"
1487
               "\nCommands:\n"
1488
               "  user [USER…]               Inspect user\n"
1489
               "  group [GROUP…]             Inspect group\n"
1490
               "  users-in-group [GROUP…]    Show users that are members of specified groups\n"
1491
               "  groups-of-user [USER…]     Show groups the specified users are members of\n"
1492
               "  services                   Show enabled database services\n"
1493
               "  ssh-authorized-keys USER   Show SSH authorized keys for user\n"
1494
               "  load-credentials           Write static user/group records from credentials\n"
1495
               "\nOptions:\n"
1496
               "  -h --help                  Show this help\n"
1497
               "     --version               Show package version\n"
1498
               "     --no-pager              Do not pipe output into a pager\n"
1499
               "     --no-legend             Do not show the headers and footers\n"
1500
               "     --output=MODE           Select output mode (classic, friendly, table, json)\n"
1501
               "  -j                         Equivalent to --output=json\n"
1502
               "  -s --service=SERVICE[:SERVICE…]\n"
1503
               "                             Query the specified service\n"
1504
               "     --with-nss=BOOL         Control whether to include glibc NSS data\n"
1505
               "  -N                         Do not synthesize or include glibc NSS data\n"
1506
               "                             (Same as --synthesize=no --with-nss=no)\n"
1507
               "     --synthesize=BOOL       Synthesize root/nobody user\n"
1508
               "     --with-dropin=BOOL      Control whether to include drop-in records\n"
1509
               "     --with-varlink=BOOL     Control whether to talk to services at all\n"
1510
               "     --multiplexer=BOOL      Control whether to use the multiplexer\n"
1511
               "     --json=pretty|short     JSON output mode\n"
1512
               "     --chain                 Chain another command\n"
1513
               "     --uid-min=ID            Filter by minimum UID/GID (default 0)\n"
1514
               "     --uid-max=ID            Filter by maximum UID/GID (default 4294967294)\n"
1515
               "  -z --fuzzy                 Do a fuzzy name search\n"
1516
               "     --disposition=VALUE     Filter by disposition\n"
1517
               "  -I                         Equivalent to --disposition=intrinsic\n"
1518
               "  -S                         Equivalent to --disposition=system\n"
1519
               "  -R                         Equivalent to --disposition=regular\n"
1520
               "     --boundaries=BOOL       Show/hide UID/GID range boundaries in output\n"
1521
               "  -B                         Equivalent to --boundaries=no\n"
1522
               "  -F --from-file=PATH        Read JSON record from file\n"
1523
               "\nSee the %s for details.\n",
1524
               program_invocation_short_name,
1525
               ansi_highlight(),
1526
               ansi_normal(),
1527
               link);
1528

1529
        return 0;
1530
}
1531

1532
static int parse_argv(int argc, char *argv[]) {
309✔
1533

1534
        enum {
309✔
1535
                ARG_VERSION = 0x100,
1536
                ARG_NO_PAGER,
1537
                ARG_NO_LEGEND,
1538
                ARG_OUTPUT,
1539
                ARG_WITH_NSS,
1540
                ARG_WITH_DROPIN,
1541
                ARG_WITH_VARLINK,
1542
                ARG_SYNTHESIZE,
1543
                ARG_MULTIPLEXER,
1544
                ARG_JSON,
1545
                ARG_CHAIN,
1546
                ARG_UID_MIN,
1547
                ARG_UID_MAX,
1548
                ARG_DISPOSITION,
1549
                ARG_BOUNDARIES,
1550
        };
1551

1552
        static const struct option options[] = {
309✔
1553
                { "help",         no_argument,       NULL, 'h'              },
1554
                { "version",      no_argument,       NULL, ARG_VERSION      },
1555
                { "no-pager",     no_argument,       NULL, ARG_NO_PAGER     },
1556
                { "no-legend",    no_argument,       NULL, ARG_NO_LEGEND    },
1557
                { "output",       required_argument, NULL, ARG_OUTPUT       },
1558
                { "service",      required_argument, NULL, 's'              },
1559
                { "with-nss",     required_argument, NULL, ARG_WITH_NSS     },
1560
                { "with-dropin",  required_argument, NULL, ARG_WITH_DROPIN  },
1561
                { "with-varlink", required_argument, NULL, ARG_WITH_VARLINK },
1562
                { "synthesize",   required_argument, NULL, ARG_SYNTHESIZE   },
1563
                { "multiplexer",  required_argument, NULL, ARG_MULTIPLEXER  },
1564
                { "json",         required_argument, NULL, ARG_JSON         },
1565
                { "chain",        no_argument,       NULL, ARG_CHAIN        },
1566
                { "uid-min",      required_argument, NULL, ARG_UID_MIN      },
1567
                { "uid-max",      required_argument, NULL, ARG_UID_MAX      },
1568
                { "fuzzy",        no_argument,       NULL, 'z'              },
1569
                { "disposition",  required_argument, NULL, ARG_DISPOSITION  },
1570
                { "boundaries",   required_argument, NULL, ARG_BOUNDARIES   },
1571
                { "from-file",    required_argument, NULL, 'F'              },
1572
                {}
1573
        };
1574

1575
        const char *e;
309✔
1576
        int r;
309✔
1577

1578
        assert(argc >= 0);
309✔
1579
        assert(argv);
309✔
1580

1581
        /* We are going to update this environment variable with our own, hence let's first read what is already set */
1582
        e = getenv("SYSTEMD_ONLY_USERDB");
309✔
1583
        if (e) {
309✔
UNCOV
1584
                char **l;
×
1585

1586
                l = strv_split(e, ":");
×
1587
                if (!l)
×
UNCOV
1588
                        return log_oom();
×
1589

1590
                strv_free(arg_services);
×
UNCOV
1591
                arg_services = l;
×
1592
        }
1593

1594
        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
1595
         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
1596
        optind = 0;
309✔
1597

1598
        for (;;) {
400✔
1599
                int c;
400✔
1600

1601
                c = getopt_long(argc, argv,
400✔
1602
                                arg_chain ? "+hjs:NISRzBF:" : "hjs:NISRzBF:", /* When --chain was used disable parsing of further switches */
400✔
1603
                                options, NULL);
1604
                if (c < 0)
400✔
1605
                        break;
1606

1607
                switch (c) {
121✔
1608

1609
                case 'h':
1✔
1610
                        return help(0, NULL, NULL);
1✔
1611

1612
                case ARG_VERSION:
1✔
1613
                        return version();
1✔
1614

1615
                case ARG_NO_PAGER:
×
1616
                        arg_pager_flags |= PAGER_DISABLE;
×
UNCOV
1617
                        break;
×
1618

1619
                case ARG_NO_LEGEND:
1✔
1620
                        arg_legend = false;
1✔
1621
                        break;
1✔
1622

1623
                case ARG_OUTPUT:
8✔
1624
                        if (isempty(optarg))
8✔
UNCOV
1625
                                arg_output = _OUTPUT_INVALID;
×
1626
                        else if (streq(optarg, "classic"))
8✔
1627
                                arg_output = OUTPUT_CLASSIC;
1✔
1628
                        else if (streq(optarg, "friendly"))
7✔
1629
                                arg_output = OUTPUT_FRIENDLY;
1✔
1630
                        else if (streq(optarg, "json"))
6✔
1631
                                arg_output = OUTPUT_JSON;
1✔
1632
                        else if (streq(optarg, "table"))
5✔
1633
                                arg_output = OUTPUT_TABLE;
1✔
1634
                        else if (streq(optarg, "help")) {
4✔
UNCOV
1635
                                puts("classic\n"
×
1636
                                     "friendly\n"
1637
                                     "json\n"
1638
                                     "table");
UNCOV
1639
                                return 0;
×
1640
                        } else
1641
                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid --output= mode: %s", optarg);
4✔
1642

1643
                        arg_json_format_flags = arg_output == OUTPUT_JSON ? SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO : SD_JSON_FORMAT_OFF;
4✔
1644
                        break;
4✔
1645

1646
                case ARG_JSON:
6✔
1647
                        r = parse_json_argument(optarg, &arg_json_format_flags);
6✔
1648
                        if (r <= 0)
6✔
1649
                                return r;
1650

1651
                        arg_output = sd_json_format_enabled(arg_json_format_flags) ? OUTPUT_JSON : _OUTPUT_INVALID;
2✔
1652
                        break;
2✔
1653

1654
                case 'j':
26✔
1655
                        arg_json_format_flags = SD_JSON_FORMAT_PRETTY|SD_JSON_FORMAT_COLOR_AUTO;
26✔
1656
                        arg_output = OUTPUT_JSON;
26✔
1657
                        break;
26✔
1658

1659
                case 's':
×
1660
                        if (isempty(optarg))
×
UNCOV
1661
                                arg_services = strv_free(arg_services);
×
1662
                        else {
1663
                                r = strv_split_and_extend(&arg_services, optarg, ":", /* filter_duplicates = */ true);
×
1664
                                if (r < 0)
×
UNCOV
1665
                                        return log_error_errno(r, "Failed to parse -s/--service= argument: %m");
×
1666
                        }
1667

1668
                        break;
1669

1670
                case 'N':
1✔
1671
                        arg_userdb_flags |= USERDB_EXCLUDE_NSS|USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN;
1✔
1672
                        break;
1✔
1673

1674
                case ARG_WITH_NSS:
14✔
1675
                        r = parse_boolean_argument("--with-nss=", optarg, NULL);
14✔
1676
                        if (r < 0)
14✔
1677
                                return r;
1678

1679
                        SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_NSS, !r);
10✔
1680
                        break;
10✔
1681

1682
                case ARG_WITH_DROPIN:
8✔
1683
                        r = parse_boolean_argument("--with-dropin=", optarg, NULL);
8✔
1684
                        if (r < 0)
8✔
1685
                                return r;
1686

1687
                        SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_DROPIN, !r);
4✔
1688
                        break;
4✔
1689

1690
                case ARG_WITH_VARLINK:
7✔
1691
                        r = parse_boolean_argument("--with-varlink=", optarg, NULL);
7✔
1692
                        if (r < 0)
7✔
1693
                                return r;
1694

1695
                        SET_FLAG(arg_userdb_flags, USERDB_EXCLUDE_VARLINK, !r);
3✔
1696
                        break;
3✔
1697

1698
                case ARG_SYNTHESIZE:
12✔
1699
                        r = parse_boolean_argument("--synthesize=", optarg, NULL);
12✔
1700
                        if (r < 0)
12✔
1701
                                return r;
1702

1703
                        SET_FLAG(arg_userdb_flags, USERDB_DONT_SYNTHESIZE_INTRINSIC|USERDB_DONT_SYNTHESIZE_FOREIGN, !r);
8✔
1704
                        break;
8✔
1705

1706
                case ARG_MULTIPLEXER:
6✔
1707
                        r = parse_boolean_argument("--multiplexer=", optarg, NULL);
6✔
1708
                        if (r < 0)
6✔
1709
                                return r;
1710

1711
                        SET_FLAG(arg_userdb_flags, USERDB_AVOID_MULTIPLEXER, !r);
2✔
1712
                        break;
2✔
1713

1714
                case ARG_CHAIN:
4✔
1715
                        arg_chain = true;
4✔
1716
                        break;
4✔
1717

1718
                case ARG_DISPOSITION: {
4✔
1719
                        UserDisposition d = user_disposition_from_string(optarg);
4✔
1720
                        if (d < 0)
4✔
UNCOV
1721
                                return log_error_errno(d, "Unknown user disposition: %s", optarg);
×
1722

1723
                        if (arg_disposition_mask == UINT64_MAX)
4✔
1724
                                arg_disposition_mask = 0;
2✔
1725

1726
                        arg_disposition_mask |= UINT64_C(1) << d;
4✔
1727
                        break;
4✔
1728
                }
1729

1730
                case 'I':
2✔
1731
                        if (arg_disposition_mask == UINT64_MAX)
2✔
1732
                                arg_disposition_mask = 0;
2✔
1733

1734
                        arg_disposition_mask |= UINT64_C(1) << USER_INTRINSIC;
2✔
1735
                        break;
2✔
1736

1737
                case 'S':
4✔
1738
                        if (arg_disposition_mask == UINT64_MAX)
4✔
1739
                                arg_disposition_mask = 0;
2✔
1740

1741
                        arg_disposition_mask |= UINT64_C(1) << USER_SYSTEM;
4✔
1742
                        break;
4✔
1743

1744
                case 'R':
2✔
1745
                        if (arg_disposition_mask == UINT64_MAX)
2✔
1746
                                arg_disposition_mask = 0;
2✔
1747

1748
                        arg_disposition_mask |= UINT64_C(1) << USER_REGULAR;
2✔
1749
                        break;
2✔
1750

1751
                case ARG_UID_MIN:
2✔
1752
                        r = parse_uid(optarg, &arg_uid_min);
2✔
1753
                        if (r < 0)
2✔
UNCOV
1754
                                return log_error_errno(r, "Failed to parse --uid-min= value: %s", optarg);
×
1755
                        break;
1756

1757
                case ARG_UID_MAX:
2✔
1758
                        r = parse_uid(optarg, &arg_uid_max);
2✔
1759
                        if (r < 0)
2✔
UNCOV
1760
                                return log_error_errno(r, "Failed to parse --uid-max= value: %s", optarg);
×
1761
                        break;
1762

1763
                case 'z':
2✔
1764
                        arg_fuzzy = true;
2✔
1765
                        break;
2✔
1766

1767
                case ARG_BOUNDARIES:
×
1768
                        r = parse_boolean_argument("boundaries", optarg, &arg_boundaries);
×
UNCOV
1769
                        if (r < 0)
×
1770
                                return r;
1771
                        break;
1772

1773
                case 'B':
2✔
1774
                        arg_boundaries = false;
2✔
1775
                        break;
2✔
1776

1777
                case 'F': {
6✔
1778
                        if (isempty(optarg)) {
6✔
UNCOV
1779
                                arg_from_file = sd_json_variant_unref(arg_from_file);
×
1780
                                break;
6✔
1781
                        }
1782

1783
                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
6✔
1784
                        const char *fn = streq(optarg, "-") ? NULL : optarg;
6✔
1785
                        unsigned line = 0;
6✔
1786
                        r = sd_json_parse_file(fn ? NULL : stdin, fn ?: "<stdin>", SD_JSON_PARSE_SENSITIVE, &v, &line, /* reterr_column= */ NULL);
6✔
1787
                        if (r < 0)
6✔
UNCOV
1788
                                return log_syntax(/* unit= */ NULL, LOG_ERR, fn ?: "<stdin>", line, r, "JSON parse failure.");
×
1789

1790
                        sd_json_variant_unref(arg_from_file);
6✔
1791
                        arg_from_file = TAKE_PTR(v);
6✔
1792
                        break;
6✔
1793
                }
1794

1795
                case '?':
1796
                        return -EINVAL;
1797

1798
                default:
×
UNCOV
1799
                        assert_not_reached();
×
1800
                }
1801
        }
1802

1803
        if (arg_uid_min > arg_uid_max)
279✔
UNCOV
1804
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Minimum UID/GID " UID_FMT " is above maximum UID/GID " UID_FMT ", refusing.", arg_uid_min, arg_uid_max);
×
1805

1806
        /* If not mask was specified, use the all bits on mask */
1807
        if (arg_disposition_mask == UINT64_MAX)
279✔
1808
                arg_disposition_mask = USER_DISPOSITION_MASK_ALL;
271✔
1809

1810
        if (arg_from_file)
279✔
1811
                arg_boundaries = false;
6✔
1812

1813
        return 1;
1814
}
1815

1816
static int run(int argc, char *argv[]) {
309✔
1817
        static const Verb verbs[] = {
309✔
1818
                { "help",                VERB_ANY, VERB_ANY, 0,            help                },
1819
                { "user",                VERB_ANY, VERB_ANY, VERB_DEFAULT, display_user        },
1820
                { "group",               VERB_ANY, VERB_ANY, 0,            display_group       },
1821
                { "users-in-group",      VERB_ANY, VERB_ANY, 0,            display_memberships },
1822
                { "groups-of-user",      VERB_ANY, VERB_ANY, 0,            display_memberships },
1823
                { "services",            VERB_ANY, 1,        0,            display_services    },
1824
                { "ssh-authorized-keys", 2,        VERB_ANY, 0,            ssh_authorized_keys },
1825
                { "load-credentials",    VERB_ANY, 1,        0,            load_credentials    },
1826
                {}
1827
        };
1828

1829
        int r;
309✔
1830

1831
        log_setup();
309✔
1832

1833
        r = parse_argv(argc, argv);
309✔
1834
        if (r <= 0)
309✔
1835
                return r;
1836

1837
        if (arg_services) {
279✔
UNCOV
1838
                _cleanup_free_ char *e = NULL;
×
1839

1840
                e = strv_join(arg_services, ":");
×
1841
                if (!e)
×
UNCOV
1842
                        return log_oom();
×
1843

1844
                if (setenv("SYSTEMD_ONLY_USERDB", e, true) < 0)
×
UNCOV
1845
                        return log_error_errno(r, "Failed to set $SYSTEMD_ONLY_USERDB: %m");
×
1846

UNCOV
1847
                log_info("Enabled services: %s", e);
×
1848
        } else
1849
                assert_se(unsetenv("SYSTEMD_ONLY_USERDB") == 0);
279✔
1850

1851
        return dispatch_verb(argc, argv, verbs, NULL);
279✔
1852
}
1853

1854
DEFINE_MAIN_FUNCTION(run);
309✔
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