1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "dirent-util.h"
8 #include "errno-list.h"
11 #include "format-table.h"
12 #include "format-util.h"
13 #include "main-func.h"
15 #include "parse-argument.h"
16 #include "parse-util.h"
17 #include "pretty-print.h"
18 #include "socket-util.h"
20 #include "terminal-util.h"
21 #include "uid-range.h"
22 #include "user-record-show.h"
23 #include "user-util.h"
32 _OUTPUT_INVALID
= -EINVAL
,
33 } arg_output
= _OUTPUT_INVALID
;
35 static PagerFlags arg_pager_flags
= 0;
36 static bool arg_legend
= true;
37 static char** arg_services
= NULL
;
38 static UserDBFlags arg_userdb_flags
= 0;
39 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
40 static bool arg_chain
= false;
42 STATIC_DESTRUCTOR_REGISTER(arg_services
, strv_freep
);
44 static const char *user_disposition_to_color(UserDisposition d
) {
46 assert(d
< _USER_DISPOSITION_MAX
);
67 static int show_user(UserRecord
*ur
, Table
*table
) {
75 if (!uid_is_valid(ur
->uid
))
78 printf("%s:x:" UID_FMT
":" GID_FMT
":%s:%s:%s\n",
82 strempty(user_record_real_name(ur
)),
83 user_record_home_directory(ur
),
84 user_record_shell(ur
));
89 json_variant_dump(ur
->json
, arg_json_format_flags
, NULL
, 0);
93 user_record_show(ur
, true);
97 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur
->user_name
);
106 d
= user_record_disposition(ur
);
111 TABLE_STRING
, ur
->user_name
,
112 TABLE_SET_COLOR
, user_disposition_to_color(d
),
113 TABLE_STRING
, user_disposition_to_string(d
),
115 TABLE_GID
, user_record_gid(ur
),
116 TABLE_STRING
, empty_to_null(ur
->real_name
),
117 TABLE_STRING
, user_record_home_directory(ur
),
118 TABLE_STRING
, user_record_shell(ur
),
121 return table_log_add_error(r
);
127 assert_not_reached();
133 static const struct {
136 UserDisposition disposition
;
137 } uid_range_table
[] = {
140 .last
= SYSTEM_UID_MAX
,
142 .disposition
= USER_SYSTEM
,
145 .first
= DYNAMIC_UID_MIN
,
146 .last
= DYNAMIC_UID_MAX
,
147 .name
= "dynamic system",
148 .disposition
= USER_DYNAMIC
,
151 .first
= CONTAINER_UID_BASE_MIN
,
152 .last
= CONTAINER_UID_BASE_MAX
,
154 .disposition
= USER_CONTAINER
,
158 .first
= HOME_UID_MIN
,
159 .last
= HOME_UID_MAX
,
160 .name
= "systemd-homed",
161 .disposition
= USER_REGULAR
,
165 .first
= MAP_UID_MIN
,
168 .disposition
= USER_REGULAR
,
172 static int table_add_uid_boundaries(Table
*table
, const UIDRange
*p
) {
177 FOREACH_ARRAY(i
, uid_range_table
, ELEMENTSOF(uid_range_table
)) {
178 _cleanup_free_
char *name
= NULL
, *comment
= NULL
;
180 if (!uid_range_covers(p
, i
->first
, i
->last
- i
->first
+ 1))
183 name
= strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN
),
184 " begin ", i
->name
, " users ",
185 special_glyph(SPECIAL_GLYPH_ARROW_DOWN
));
189 comment
= strjoin("First ", i
->name
, " user");
195 TABLE_STRING
, special_glyph(SPECIAL_GLYPH_TREE_TOP
),
197 TABLE_SET_COLOR
, ansi_grey(),
198 TABLE_STRING
, user_disposition_to_string(i
->disposition
),
199 TABLE_SET_COLOR
, ansi_grey(),
201 TABLE_SET_COLOR
, ansi_grey(),
203 TABLE_STRING
, comment
,
204 TABLE_SET_COLOR
, ansi_grey(),
207 TABLE_INT
, -1); /* sort before any other entry with the same UID */
209 return table_log_add_error(r
);
212 name
= strjoin(special_glyph(SPECIAL_GLYPH_ARROW_UP
),
213 " end ", i
->name
, " users ",
214 special_glyph(SPECIAL_GLYPH_ARROW_UP
));
219 comment
= strjoin("Last ", i
->name
, " user");
225 TABLE_STRING
, special_glyph(SPECIAL_GLYPH_TREE_RIGHT
),
227 TABLE_SET_COLOR
, ansi_grey(),
228 TABLE_STRING
, user_disposition_to_string(i
->disposition
),
229 TABLE_SET_COLOR
, ansi_grey(),
231 TABLE_SET_COLOR
, ansi_grey(),
233 TABLE_STRING
, comment
,
234 TABLE_SET_COLOR
, ansi_grey(),
237 TABLE_INT
, 1); /* sort after any other entry with the same UID */
239 return table_log_add_error(r
);
242 return ELEMENTSOF(uid_range_table
) * 2;
245 static int add_unavailable_uid(Table
*table
, uid_t start
, uid_t end
) {
246 _cleanup_free_
char *name
= NULL
;
250 assert(start
<= end
);
252 name
= strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN
),
253 " begin unavailable users ",
254 special_glyph(SPECIAL_GLYPH_ARROW_DOWN
));
260 TABLE_STRING
, special_glyph(SPECIAL_GLYPH_TREE_TOP
),
262 TABLE_SET_COLOR
, ansi_grey(),
265 TABLE_SET_COLOR
, ansi_grey(),
267 TABLE_STRING
, "First unavailable user",
268 TABLE_SET_COLOR
, ansi_grey(),
271 TABLE_INT
, -1); /* sort before an other entry with the same UID */
273 return table_log_add_error(r
);
276 name
= strjoin(special_glyph(SPECIAL_GLYPH_ARROW_UP
),
277 " end unavailable users ",
278 special_glyph(SPECIAL_GLYPH_ARROW_UP
));
284 TABLE_STRING
, special_glyph(SPECIAL_GLYPH_TREE_RIGHT
),
286 TABLE_SET_COLOR
, ansi_grey(),
289 TABLE_SET_COLOR
, ansi_grey(),
291 TABLE_STRING
, "Last unavailable user",
292 TABLE_SET_COLOR
, ansi_grey(),
295 TABLE_INT
, 1); /* sort after any other entry with the same UID */
297 return table_log_add_error(r
);
302 static int table_add_uid_map(
305 int (*add_unavailable
)(Table
*t
, uid_t start
, uid_t end
)) {
311 assert(add_unavailable
);
316 FOREACH_ARRAY(x
, p
->entries
, p
->n_entries
) {
317 if (focus
< x
->start
) {
318 r
= add_unavailable(table
, focus
, x
->start
-1);
325 if (x
->start
> UINT32_MAX
- x
->nr
) { /* overflow check */
330 focus
= x
->start
+ x
->nr
;
333 if (focus
< UINT32_MAX
-1) {
334 r
= add_unavailable(table
, focus
, UINT32_MAX
-1);
344 static int display_user(int argc
, char *argv
[], void *userdata
) {
345 _cleanup_(table_unrefp
) Table
*table
= NULL
;
346 bool draw_separator
= false;
350 arg_output
= argc
> 1 ? OUTPUT_FRIENDLY
: OUTPUT_TABLE
;
352 if (arg_output
== OUTPUT_TABLE
) {
353 table
= table_new(" ", "name", "disposition", "uid", "gid", "realname", "home", "shell", "order");
357 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 3), 100);
358 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 4), 100);
359 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
360 (void) table_set_sort(table
, (size_t) 3, (size_t) 8);
361 (void) table_set_display(table
, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4, (size_t) 5, (size_t) 6, (size_t) 7);
365 STRV_FOREACH(i
, argv
+ 1) {
366 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
369 if (parse_uid(*i
, &uid
) >= 0)
370 r
= userdb_by_uid(uid
, arg_userdb_flags
, &ur
);
372 r
= userdb_by_name(*i
, arg_userdb_flags
, &ur
);
375 log_error_errno(r
, "User %s does not exist.", *i
);
376 else if (r
== -EHOSTDOWN
)
377 log_error_errno(r
, "Selected user database service is not available for this request.");
379 log_error_errno(r
, "Failed to find user %s: %m", *i
);
384 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
387 r
= show_user(ur
, table
);
391 draw_separator
= true;
395 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
397 r
= userdb_all(arg_userdb_flags
, &iterator
);
398 if (r
== -ENOLINK
) /* ENOLINK → Didn't find answer without Varlink, and didn't try Varlink because was configured to off. */
399 log_debug_errno(r
, "No entries found. (Didn't check via Varlink.)");
400 else if (r
== -ESRCH
) /* ESRCH → Couldn't find any suitable entry, but we checked all sources */
401 log_debug_errno(r
, "No entries found.");
403 return log_error_errno(r
, "Failed to enumerate users: %m");
406 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
408 r
= userdb_iterator_get(iterator
, &ur
);
412 return log_error_errno(r
, "Selected user database service is not available for this request.");
414 return log_error_errno(r
, "Failed acquire next user: %m");
416 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
419 r
= show_user(ur
, table
);
423 draw_separator
= true;
429 _cleanup_(uid_range_freep
) UIDRange
*uid_range
= NULL
;
430 int boundary_lines
, uid_map_lines
;
432 r
= uid_range_load_userns(/* path = */ NULL
, UID_RANGE_USERNS_INSIDE
, &uid_range
);
434 log_debug_errno(r
, "Failed to load /proc/self/uid_map, ignoring: %m");
436 boundary_lines
= table_add_uid_boundaries(table
, uid_range
);
437 if (boundary_lines
< 0)
438 return boundary_lines
;
440 uid_map_lines
= table_add_uid_map(table
, uid_range
, add_unavailable_uid
);
441 if (uid_map_lines
< 0)
442 return uid_map_lines
;
444 if (!table_isempty(table
)) {
445 r
= table_print_with_pager(table
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
447 return table_log_print_error(r
);
453 k
= table_get_rows(table
) - 1 - boundary_lines
- uid_map_lines
;
455 printf("\n%zu users listed.\n", k
);
457 printf("No users.\n");
464 static int show_group(GroupRecord
*gr
, Table
*table
) {
469 switch (arg_output
) {
471 case OUTPUT_CLASSIC
: {
472 _cleanup_free_
char *m
= NULL
;
474 if (!gid_is_valid(gr
->gid
))
477 m
= strv_join(gr
->members
, ",");
481 printf("%s:x:" GID_FMT
":%s\n",
489 json_variant_dump(gr
->json
, arg_json_format_flags
, NULL
, 0);
492 case OUTPUT_FRIENDLY
:
493 group_record_show(gr
, true);
495 if (gr
->incomplete
) {
497 log_warning("Warning: lacking rights to acquire privileged fields of group record of '%s', output incomplete.", gr
->group_name
);
506 d
= group_record_disposition(gr
);
511 TABLE_STRING
, gr
->group_name
,
512 TABLE_SET_COLOR
, user_disposition_to_color(d
),
513 TABLE_STRING
, user_disposition_to_string(d
),
515 TABLE_STRING
, gr
->description
,
518 return table_log_add_error(r
);
524 assert_not_reached();
530 static int table_add_gid_boundaries(Table
*table
, const UIDRange
*p
) {
535 FOREACH_ARRAY(i
, uid_range_table
, ELEMENTSOF(uid_range_table
)) {
536 _cleanup_free_
char *name
= NULL
, *comment
= NULL
;
538 if (!uid_range_covers(p
, i
->first
, i
->last
- i
->first
+ 1))
541 name
= strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN
),
542 " begin ", i
->name
, " groups ",
543 special_glyph(SPECIAL_GLYPH_ARROW_DOWN
));
547 comment
= strjoin("First ", i
->name
, " group");
553 TABLE_STRING
, special_glyph(SPECIAL_GLYPH_TREE_TOP
),
555 TABLE_SET_COLOR
, ansi_grey(),
556 TABLE_STRING
, user_disposition_to_string(i
->disposition
),
557 TABLE_SET_COLOR
, ansi_grey(),
559 TABLE_SET_COLOR
, ansi_grey(),
560 TABLE_STRING
, comment
,
561 TABLE_SET_COLOR
, ansi_grey(),
562 TABLE_INT
, -1); /* sort before any other entry with the same GID */
564 return table_log_add_error(r
);
567 name
= strjoin(special_glyph(SPECIAL_GLYPH_ARROW_UP
),
568 " end ", i
->name
, " groups ",
569 special_glyph(SPECIAL_GLYPH_ARROW_UP
));
574 comment
= strjoin("Last ", i
->name
, " group");
580 TABLE_STRING
, special_glyph(SPECIAL_GLYPH_TREE_RIGHT
),
582 TABLE_SET_COLOR
, ansi_grey(),
583 TABLE_STRING
, user_disposition_to_string(i
->disposition
),
584 TABLE_SET_COLOR
, ansi_grey(),
586 TABLE_SET_COLOR
, ansi_grey(),
587 TABLE_STRING
, comment
,
588 TABLE_SET_COLOR
, ansi_grey(),
589 TABLE_INT
, 1); /* sort after any other entry with the same GID */
591 return table_log_add_error(r
);
594 return ELEMENTSOF(uid_range_table
) * 2;
597 static int add_unavailable_gid(Table
*table
, uid_t start
, uid_t end
) {
598 _cleanup_free_
char *name
= NULL
;
602 assert(start
<= end
);
604 name
= strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN
),
605 " begin unavailable groups ",
606 special_glyph(SPECIAL_GLYPH_ARROW_DOWN
));
612 TABLE_STRING
, special_glyph(SPECIAL_GLYPH_TREE_TOP
),
614 TABLE_SET_COLOR
, ansi_grey(),
617 TABLE_SET_COLOR
, ansi_grey(),
618 TABLE_STRING
, "First unavailable group",
619 TABLE_SET_COLOR
, ansi_grey(),
620 TABLE_INT
, -1); /* sort before any other entry with the same GID */
622 return table_log_add_error(r
);
625 name
= strjoin(special_glyph(SPECIAL_GLYPH_ARROW_UP
),
626 " end unavailable groups ",
627 special_glyph(SPECIAL_GLYPH_ARROW_UP
));
633 TABLE_STRING
, special_glyph(SPECIAL_GLYPH_TREE_RIGHT
),
635 TABLE_SET_COLOR
, ansi_grey(),
638 TABLE_SET_COLOR
, ansi_grey(),
639 TABLE_STRING
, "Last unavailable group",
640 TABLE_SET_COLOR
, ansi_grey(),
641 TABLE_INT
, 1); /* sort after any other entry with the same GID */
643 return table_log_add_error(r
);
648 static int display_group(int argc
, char *argv
[], void *userdata
) {
649 _cleanup_(table_unrefp
) Table
*table
= NULL
;
650 bool draw_separator
= false;
654 arg_output
= argc
> 1 ? OUTPUT_FRIENDLY
: OUTPUT_TABLE
;
656 if (arg_output
== OUTPUT_TABLE
) {
657 table
= table_new(" ", "name", "disposition", "gid", "description", "order");
661 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 3), 100);
662 table_set_ersatz_string(table
, TABLE_ERSATZ_DASH
);
663 (void) table_set_sort(table
, (size_t) 3, (size_t) 5);
664 (void) table_set_display(table
, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 4);
668 STRV_FOREACH(i
, argv
+ 1) {
669 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
672 if (parse_gid(*i
, &gid
) >= 0)
673 r
= groupdb_by_gid(gid
, arg_userdb_flags
, &gr
);
675 r
= groupdb_by_name(*i
, arg_userdb_flags
, &gr
);
678 log_error_errno(r
, "Group %s does not exist.", *i
);
679 else if (r
== -EHOSTDOWN
)
680 log_error_errno(r
, "Selected group database service is not available for this request.");
682 log_error_errno(r
, "Failed to find group %s: %m", *i
);
687 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
690 r
= show_group(gr
, table
);
694 draw_separator
= true;
698 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
700 r
= groupdb_all(arg_userdb_flags
, &iterator
);
702 log_debug_errno(r
, "No entries found. (Didn't check via Varlink.)");
703 else if (r
== -ESRCH
)
704 log_debug_errno(r
, "No entries found.");
706 return log_error_errno(r
, "Failed to enumerate groups: %m");
709 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
711 r
= groupdb_iterator_get(iterator
, &gr
);
715 return log_error_errno(r
, "Selected group database service is not available for this request.");
717 return log_error_errno(r
, "Failed acquire next group: %m");
719 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
722 r
= show_group(gr
, table
);
726 draw_separator
= true;
732 _cleanup_(uid_range_freep
) UIDRange
*gid_range
= NULL
;
733 int boundary_lines
, gid_map_lines
;
735 r
= uid_range_load_userns(/* path = */ NULL
, GID_RANGE_USERNS_INSIDE
, &gid_range
);
737 log_debug_errno(r
, "Failed to load /proc/self/gid_map, ignoring: %m");
739 boundary_lines
= table_add_gid_boundaries(table
, gid_range
);
740 if (boundary_lines
< 0)
741 return boundary_lines
;
743 gid_map_lines
= table_add_uid_map(table
, gid_range
, add_unavailable_gid
);
744 if (gid_map_lines
< 0)
745 return gid_map_lines
;
747 if (!table_isempty(table
)) {
748 r
= table_print_with_pager(table
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
750 return table_log_print_error(r
);
756 k
= table_get_rows(table
) - 1 - boundary_lines
- gid_map_lines
;
758 printf("\n%zu groups listed.\n", k
);
760 printf("No groups.\n");
767 static int show_membership(const char *user
, const char *group
, Table
*table
) {
773 switch (arg_output
) {
776 /* Strictly speaking there's no 'classic' output for this concept, but let's output it in
777 * similar style to the classic output for user/group info */
779 printf("%s:%s\n", user
, group
);
783 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
785 r
= json_build(&v
, JSON_BUILD_OBJECT(
786 JSON_BUILD_PAIR("user", JSON_BUILD_STRING(user
)),
787 JSON_BUILD_PAIR("group", JSON_BUILD_STRING(group
))));
789 return log_error_errno(r
, "Failed to build JSON object: %m");
791 json_variant_dump(v
, arg_json_format_flags
, NULL
, NULL
);
795 case OUTPUT_FRIENDLY
:
796 /* Hmm, this is not particularly friendly, but not sure how we could do this better */
797 printf("%s: %s\n", group
, user
);
806 TABLE_STRING
, group
);
808 return table_log_add_error(r
);
813 assert_not_reached();
819 static int display_memberships(int argc
, char *argv
[], void *userdata
) {
820 _cleanup_(table_unrefp
) Table
*table
= NULL
;
824 arg_output
= OUTPUT_TABLE
;
826 if (arg_output
== OUTPUT_TABLE
) {
827 table
= table_new("user", "group");
831 (void) table_set_sort(table
, (size_t) 0, (size_t) 1);
835 STRV_FOREACH(i
, argv
+ 1) {
836 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
838 if (streq(argv
[0], "users-in-group")) {
839 r
= membershipdb_by_group(*i
, arg_userdb_flags
, &iterator
);
841 return log_error_errno(r
, "Failed to enumerate users in group: %m");
842 } else if (streq(argv
[0], "groups-of-user")) {
843 r
= membershipdb_by_user(*i
, arg_userdb_flags
, &iterator
);
845 return log_error_errno(r
, "Failed to enumerate groups of user: %m");
847 assert_not_reached();
850 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
852 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
856 return log_error_errno(r
, "Selected membership database service is not available for this request.");
858 return log_error_errno(r
, "Failed acquire next membership: %m");
860 r
= show_membership(user
, group
, table
);
866 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
868 r
= membershipdb_all(arg_userdb_flags
, &iterator
);
870 log_debug_errno(r
, "No entries found. (Didn't check via Varlink.)");
871 else if (r
== -ESRCH
)
872 log_debug_errno(r
, "No entries found.");
874 return log_error_errno(r
, "Failed to enumerate memberships: %m");
877 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
879 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
883 return log_error_errno(r
, "Selected membership database service is not available for this request.");
885 return log_error_errno(r
, "Failed acquire next membership: %m");
887 r
= show_membership(user
, group
, table
);
895 if (!table_isempty(table
)) {
896 r
= table_print_with_pager(table
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
898 return table_log_print_error(r
);
902 if (table_isempty(table
))
903 printf("No memberships.\n");
905 printf("\n%zu memberships listed.\n", table_get_rows(table
) - 1);
912 static int display_services(int argc
, char *argv
[], void *userdata
) {
913 _cleanup_(table_unrefp
) Table
*t
= NULL
;
914 _cleanup_closedir_
DIR *d
= NULL
;
917 d
= opendir("/run/systemd/userdb/");
919 if (errno
== ENOENT
) {
920 log_info("No services.");
924 return log_error_errno(errno
, "Failed to open /run/systemd/userdb/: %m");
927 t
= table_new("service", "listening");
931 (void) table_set_sort(t
, (size_t) 0);
933 FOREACH_DIRENT(de
, d
, return -errno
) {
934 _cleanup_free_
char *j
= NULL
, *no
= NULL
;
935 _cleanup_close_
int fd
= -EBADF
;
937 j
= path_join("/run/systemd/userdb/", de
->d_name
);
941 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
943 return log_error_errno(errno
, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m");
945 r
= connect_unix_path(fd
, dirfd(d
), de
->d_name
);
947 no
= strjoin("No (", errno_to_name(r
), ")");
952 r
= table_add_many(t
,
953 TABLE_STRING
, de
->d_name
,
954 TABLE_STRING
, no
?: "yes",
955 TABLE_SET_COLOR
, no
? ansi_highlight_red() : ansi_highlight_green());
957 return table_log_add_error(r
);
960 if (!table_isempty(t
)) {
961 r
= table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
963 return table_log_print_error(r
);
966 if (arg_legend
&& arg_output
!= OUTPUT_JSON
) {
967 if (table_isempty(t
))
968 printf("No services.\n");
970 printf("\n%zu services listed.\n", table_get_rows(t
) - 1);
976 static int ssh_authorized_keys(int argc
, char *argv
[], void *userdata
) {
977 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
978 char **chain_invocation
;
984 /* If --chain is specified, the rest of the command line is the chain command */
987 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
988 "No chain command line specified, refusing.");
990 /* Make similar restrictions on the chain command as OpenSSH itself makes on the primary command. */
991 if (!path_is_absolute(argv
[2]))
992 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
993 "Chain invocation of ssh-authorized-keys commands requires an absolute binary path argument.");
995 if (!path_is_normalized(argv
[2]))
996 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
997 "Chain invocation of ssh-authorized-keys commands requires an normalized binary path argument.");
999 chain_invocation
= argv
+ 2;
1001 /* If --chain is not specified, then refuse any further arguments */
1004 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Too many arguments.");
1006 chain_invocation
= NULL
;
1009 r
= userdb_by_name(argv
[1], arg_userdb_flags
, &ur
);
1011 log_error_errno(r
, "User %s does not exist.", argv
[1]);
1012 else if (r
== -EHOSTDOWN
)
1013 log_error_errno(r
, "Selected user database service is not available for this request.");
1014 else if (r
== -EINVAL
)
1015 log_error_errno(r
, "Failed to find user %s: %m (Invalid user name?)", argv
[1]);
1017 log_error_errno(r
, "Failed to find user %s: %m", argv
[1]);
1019 if (strv_isempty(ur
->ssh_authorized_keys
))
1020 log_debug("User record for %s has no public SSH keys.", argv
[1]);
1022 STRV_FOREACH(i
, ur
->ssh_authorized_keys
)
1025 if (ur
->incomplete
) {
1027 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur
->user_name
);
1031 if (chain_invocation
) {
1032 if (DEBUG_LOGGING
) {
1033 _cleanup_free_
char *s
= NULL
;
1035 s
= quote_command_line(chain_invocation
, SHELL_ESCAPE_EMPTY
);
1039 log_debug("Chain invoking: %s", s
);
1043 execv(chain_invocation
[0], chain_invocation
);
1044 if (errno
== ENOENT
) /* Let's handle ENOENT gracefully */
1045 log_warning_errno(errno
, "Chain executable '%s' does not exist, ignoring chain invocation.", chain_invocation
[0]);
1047 log_error_errno(errno
, "Failed to invoke chain executable '%s': %m", chain_invocation
[0]);
1056 static int help(int argc
, char *argv
[], void *userdata
) {
1057 _cleanup_free_
char *link
= NULL
;
1060 pager_open(arg_pager_flags
);
1062 r
= terminal_urlify_man("userdbctl", "1", &link
);
1066 printf("%s [OPTIONS...] COMMAND ...\n\n"
1067 "%sShow user and group information.%s\n"
1069 " user [USER…] Inspect user\n"
1070 " group [GROUP…] Inspect group\n"
1071 " users-in-group [GROUP…] Show users that are members of specified groups\n"
1072 " groups-of-user [USER…] Show groups the specified users are members of\n"
1073 " services Show enabled database services\n"
1074 " ssh-authorized-keys USER Show SSH authorized keys for user\n"
1076 " -h --help Show this help\n"
1077 " --version Show package version\n"
1078 " --no-pager Do not pipe output into a pager\n"
1079 " --no-legend Do not show the headers and footers\n"
1080 " --output=MODE Select output mode (classic, friendly, table, json)\n"
1081 " -j Equivalent to --output=json\n"
1082 " -s --service=SERVICE[:SERVICE…]\n"
1083 " Query the specified service\n"
1084 " --with-nss=BOOL Control whether to include glibc NSS data\n"
1085 " -N Do not synthesize or include glibc NSS data\n"
1086 " (Same as --synthesize=no --with-nss=no)\n"
1087 " --synthesize=BOOL Synthesize root/nobody user\n"
1088 " --with-dropin=BOOL Control whether to include drop-in records\n"
1089 " --with-varlink=BOOL Control whether to talk to services at all\n"
1090 " --multiplexer=BOOL Control whether to use the multiplexer\n"
1091 " --json=pretty|short JSON output mode\n"
1092 " --chain Chain another command\n"
1093 "\nSee the %s for details.\n",
1094 program_invocation_short_name
,
1102 static int parse_argv(int argc
, char *argv
[]) {
1105 ARG_VERSION
= 0x100,
1118 static const struct option options
[] = {
1119 { "help", no_argument
, NULL
, 'h' },
1120 { "version", no_argument
, NULL
, ARG_VERSION
},
1121 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
1122 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
1123 { "output", required_argument
, NULL
, ARG_OUTPUT
},
1124 { "service", required_argument
, NULL
, 's' },
1125 { "with-nss", required_argument
, NULL
, ARG_WITH_NSS
},
1126 { "with-dropin", required_argument
, NULL
, ARG_WITH_DROPIN
},
1127 { "with-varlink", required_argument
, NULL
, ARG_WITH_VARLINK
},
1128 { "synthesize", required_argument
, NULL
, ARG_SYNTHESIZE
},
1129 { "multiplexer", required_argument
, NULL
, ARG_MULTIPLEXER
},
1130 { "json", required_argument
, NULL
, ARG_JSON
},
1131 { "chain", no_argument
, NULL
, ARG_CHAIN
},
1141 /* We are going to update this environment variable with our own, hence let's first read what is already set */
1142 e
= getenv("SYSTEMD_ONLY_USERDB");
1146 l
= strv_split(e
, ":");
1150 strv_free(arg_services
);
1154 /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
1155 * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
1161 c
= getopt_long(argc
, argv
,
1162 arg_chain
? "+hjs:N" : "hjs:N", /* When --chain was used disable parsing of further switches */
1170 return help(0, NULL
, NULL
);
1176 arg_pager_flags
|= PAGER_DISABLE
;
1184 if (isempty(optarg
))
1185 arg_output
= _OUTPUT_INVALID
;
1186 else if (streq(optarg
, "classic"))
1187 arg_output
= OUTPUT_CLASSIC
;
1188 else if (streq(optarg
, "friendly"))
1189 arg_output
= OUTPUT_FRIENDLY
;
1190 else if (streq(optarg
, "json"))
1191 arg_output
= OUTPUT_JSON
;
1192 else if (streq(optarg
, "table"))
1193 arg_output
= OUTPUT_TABLE
;
1194 else if (streq(optarg
, "help")) {
1201 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid --output= mode: %s", optarg
);
1203 arg_json_format_flags
= arg_output
== OUTPUT_JSON
? JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
: JSON_FORMAT_OFF
;
1207 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
1211 arg_output
= FLAGS_SET(arg_json_format_flags
, JSON_FORMAT_OFF
) ? _OUTPUT_INVALID
: OUTPUT_JSON
;
1215 arg_json_format_flags
= JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
;
1216 arg_output
= OUTPUT_JSON
;
1220 if (isempty(optarg
))
1221 arg_services
= strv_free(arg_services
);
1223 _cleanup_strv_free_
char **l
= NULL
;
1225 l
= strv_split(optarg
, ":");
1229 r
= strv_extend_strv(&arg_services
, l
, true);
1237 arg_userdb_flags
|= USERDB_EXCLUDE_NSS
|USERDB_DONT_SYNTHESIZE
;
1241 r
= parse_boolean_argument("--with-nss=", optarg
, NULL
);
1245 SET_FLAG(arg_userdb_flags
, USERDB_EXCLUDE_NSS
, !r
);
1248 case ARG_WITH_DROPIN
:
1249 r
= parse_boolean_argument("--with-dropin=", optarg
, NULL
);
1253 SET_FLAG(arg_userdb_flags
, USERDB_EXCLUDE_DROPIN
, !r
);
1256 case ARG_WITH_VARLINK
:
1257 r
= parse_boolean_argument("--with-varlink=", optarg
, NULL
);
1261 SET_FLAG(arg_userdb_flags
, USERDB_EXCLUDE_VARLINK
, !r
);
1264 case ARG_SYNTHESIZE
:
1265 r
= parse_boolean_argument("--synthesize=", optarg
, NULL
);
1269 SET_FLAG(arg_userdb_flags
, USERDB_DONT_SYNTHESIZE
, !r
);
1272 case ARG_MULTIPLEXER
:
1273 r
= parse_boolean_argument("--multiplexer=", optarg
, NULL
);
1277 SET_FLAG(arg_userdb_flags
, USERDB_AVOID_MULTIPLEXER
, !r
);
1288 assert_not_reached();
1295 static int run(int argc
, char *argv
[]) {
1296 static const Verb verbs
[] = {
1297 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
1298 { "user", VERB_ANY
, VERB_ANY
, VERB_DEFAULT
, display_user
},
1299 { "group", VERB_ANY
, VERB_ANY
, 0, display_group
},
1300 { "users-in-group", VERB_ANY
, VERB_ANY
, 0, display_memberships
},
1301 { "groups-of-user", VERB_ANY
, VERB_ANY
, 0, display_memberships
},
1302 { "services", VERB_ANY
, 1, 0, display_services
},
1304 /* This one is a helper for sshd_config's AuthorizedKeysCommand= setting, it's not a
1305 * user-facing verb and thus should not appear in man pages or --help texts. */
1306 { "ssh-authorized-keys", 2, VERB_ANY
, 0, ssh_authorized_keys
},
1314 r
= parse_argv(argc
, argv
);
1319 _cleanup_free_
char *e
= NULL
;
1321 e
= strv_join(arg_services
, ":");
1325 if (setenv("SYSTEMD_ONLY_USERDB", e
, true) < 0)
1326 return log_error_errno(r
, "Failed to set $SYSTEMD_ONLY_USERDB: %m");
1328 log_info("Enabled services: %s", e
);
1330 assert_se(unsetenv("SYSTEMD_ONLY_USERDB") == 0);
1332 return dispatch_verb(argc
, argv
, verbs
, NULL
);
1335 DEFINE_MAIN_FUNCTION(run
);