1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "dirent-util.h"
7 #include "errno-list.h"
10 #include "format-table.h"
11 #include "format-util.h"
12 #include "main-func.h"
14 #include "parse-argument.h"
15 #include "parse-util.h"
16 #include "pretty-print.h"
17 #include "socket-util.h"
19 #include "terminal-util.h"
20 #include "user-record-show.h"
21 #include "user-util.h"
30 _OUTPUT_INVALID
= -EINVAL
,
31 } arg_output
= _OUTPUT_INVALID
;
33 static PagerFlags arg_pager_flags
= 0;
34 static bool arg_legend
= true;
35 static char** arg_services
= NULL
;
36 static UserDBFlags arg_userdb_flags
= 0;
37 static JsonFormatFlags arg_json_format_flags
= JSON_FORMAT_OFF
;
38 static bool arg_chain
= false;
40 STATIC_DESTRUCTOR_REGISTER(arg_services
, strv_freep
);
42 static int show_user(UserRecord
*ur
, Table
*table
) {
50 if (!uid_is_valid(ur
->uid
))
53 printf("%s:x:" UID_FMT
":" GID_FMT
":%s:%s:%s\n",
57 strempty(user_record_real_name(ur
)),
58 user_record_home_directory(ur
),
59 user_record_shell(ur
));
64 json_variant_dump(ur
->json
, arg_json_format_flags
, NULL
, 0);
68 user_record_show(ur
, true);
72 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur
->user_name
);
82 TABLE_STRING
, ur
->user_name
,
83 TABLE_STRING
, user_disposition_to_string(user_record_disposition(ur
)),
85 TABLE_GID
, user_record_gid(ur
),
86 TABLE_STRING
, empty_to_null(ur
->real_name
),
87 TABLE_STRING
, user_record_home_directory(ur
),
88 TABLE_STRING
, user_record_shell(ur
),
89 TABLE_INT
, (int) user_record_disposition(ur
));
91 return table_log_add_error(r
);
102 static int display_user(int argc
, char *argv
[], void *userdata
) {
103 _cleanup_(table_unrefp
) Table
*table
= NULL
;
104 bool draw_separator
= false;
108 arg_output
= argc
> 1 ? OUTPUT_FRIENDLY
: OUTPUT_TABLE
;
110 if (arg_output
== OUTPUT_TABLE
) {
111 table
= table_new("name", "disposition", "uid", "gid", "realname", "home", "shell", "disposition-numeric");
115 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 2), 100);
116 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 3), 100);
117 (void) table_set_empty_string(table
, "-");
118 (void) table_set_sort(table
, (size_t) 7, (size_t) 2);
119 (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);
125 STRV_FOREACH(i
, argv
+ 1) {
126 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
129 if (parse_uid(*i
, &uid
) >= 0)
130 r
= userdb_by_uid(uid
, arg_userdb_flags
, &ur
);
132 r
= userdb_by_name(*i
, arg_userdb_flags
, &ur
);
135 log_error_errno(r
, "User %s does not exist.", *i
);
136 else if (r
== -EHOSTDOWN
)
137 log_error_errno(r
, "Selected user database service is not available for this request.");
139 log_error_errno(r
, "Failed to find user %s: %m", *i
);
144 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
147 r
= show_user(ur
, table
);
151 draw_separator
= true;
155 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
157 r
= userdb_all(arg_userdb_flags
, &iterator
);
158 if (r
== -ENOLINK
) /* ENOLINK → Didn't find answer without Varlink, and didn't try Varlink because was configured to off. */
159 log_debug_errno(r
, "No entries found. (Didn't check via Varlink.)");
160 else if (r
== -ESRCH
) /* ESRCH → Couldn't find any suitable entry, but we checked all sources */
161 log_debug_errno(r
, "No entries found.");
163 return log_error_errno(r
, "Failed to enumerate users: %m");
166 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
168 r
= userdb_iterator_get(iterator
, &ur
);
172 return log_error_errno(r
, "Selected user database service is not available for this request.");
174 return log_error_errno(r
, "Failed acquire next user: %m");
176 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
179 r
= show_user(ur
, table
);
183 draw_separator
= true;
189 if (table_get_rows(table
) > 1) {
190 r
= table_print_with_pager(table
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
192 return table_log_print_error(r
);
196 if (table_get_rows(table
) > 1)
197 printf("\n%zu users listed.\n", table_get_rows(table
) - 1);
199 printf("No users.\n");
206 static int show_group(GroupRecord
*gr
, Table
*table
) {
211 switch (arg_output
) {
213 case OUTPUT_CLASSIC
: {
214 _cleanup_free_
char *m
= NULL
;
216 if (!gid_is_valid(gr
->gid
))
219 m
= strv_join(gr
->members
, ",");
223 printf("%s:x:" GID_FMT
":%s\n",
231 json_variant_dump(gr
->json
, arg_json_format_flags
, NULL
, 0);
234 case OUTPUT_FRIENDLY
:
235 group_record_show(gr
, true);
237 if (gr
->incomplete
) {
239 log_warning("Warning: lacking rights to acquire privileged fields of group record of '%s', output incomplete.", gr
->group_name
);
249 TABLE_STRING
, gr
->group_name
,
250 TABLE_STRING
, user_disposition_to_string(group_record_disposition(gr
)),
252 TABLE_STRING
, gr
->description
,
253 TABLE_INT
, (int) group_record_disposition(gr
));
255 return table_log_add_error(r
);
260 assert_not_reached();
267 static int display_group(int argc
, char *argv
[], void *userdata
) {
268 _cleanup_(table_unrefp
) Table
*table
= NULL
;
269 bool draw_separator
= false;
273 arg_output
= argc
> 1 ? OUTPUT_FRIENDLY
: OUTPUT_TABLE
;
275 if (arg_output
== OUTPUT_TABLE
) {
276 table
= table_new("name", "disposition", "gid", "description", "disposition-numeric");
280 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 2), 100);
281 (void) table_set_empty_string(table
, "-");
282 (void) table_set_sort(table
, (size_t) 3, (size_t) 2);
283 (void) table_set_display(table
, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3);
289 STRV_FOREACH(i
, argv
+ 1) {
290 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
293 if (parse_gid(*i
, &gid
) >= 0)
294 r
= groupdb_by_gid(gid
, arg_userdb_flags
, &gr
);
296 r
= groupdb_by_name(*i
, arg_userdb_flags
, &gr
);
299 log_error_errno(r
, "Group %s does not exist.", *i
);
300 else if (r
== -EHOSTDOWN
)
301 log_error_errno(r
, "Selected group database service is not available for this request.");
303 log_error_errno(r
, "Failed to find group %s: %m", *i
);
308 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
311 r
= show_group(gr
, table
);
315 draw_separator
= true;
320 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
322 r
= groupdb_all(arg_userdb_flags
, &iterator
);
324 log_debug_errno(r
, "No entries found. (Didn't check via Varlink.)");
325 else if (r
== -ESRCH
)
326 log_debug_errno(r
, "No entries found.");
328 return log_error_errno(r
, "Failed to enumerate groups: %m");
331 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
333 r
= groupdb_iterator_get(iterator
, &gr
);
337 return log_error_errno(r
, "Selected group database service is not available for this request.");
339 return log_error_errno(r
, "Failed acquire next group: %m");
341 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
344 r
= show_group(gr
, table
);
348 draw_separator
= true;
354 if (table_get_rows(table
) > 1) {
355 r
= table_print_with_pager(table
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
357 return table_log_print_error(r
);
361 if (table_get_rows(table
) > 1)
362 printf("\n%zu groups listed.\n", table_get_rows(table
) - 1);
364 printf("No groups.\n");
371 static int show_membership(const char *user
, const char *group
, Table
*table
) {
377 switch (arg_output
) {
380 /* Strictly speaking there's no 'classic' output for this concept, but let's output it in
381 * similar style to the classic output for user/group info */
383 printf("%s:%s\n", user
, group
);
387 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
389 r
= json_build(&v
, JSON_BUILD_OBJECT(
390 JSON_BUILD_PAIR("user", JSON_BUILD_STRING(user
)),
391 JSON_BUILD_PAIR("group", JSON_BUILD_STRING(group
))));
393 return log_error_errno(r
, "Failed to build JSON object: %m");
395 json_variant_dump(v
, arg_json_format_flags
, NULL
, NULL
);
399 case OUTPUT_FRIENDLY
:
400 /* Hmm, this is not particularly friendly, but not sure how we could do this better */
401 printf("%s: %s\n", group
, user
);
410 TABLE_STRING
, group
);
412 return table_log_add_error(r
);
417 assert_not_reached();
423 static int display_memberships(int argc
, char *argv
[], void *userdata
) {
424 _cleanup_(table_unrefp
) Table
*table
= NULL
;
428 arg_output
= OUTPUT_TABLE
;
430 if (arg_output
== OUTPUT_TABLE
) {
431 table
= table_new("user", "group");
435 (void) table_set_sort(table
, (size_t) 0, (size_t) 1);
441 STRV_FOREACH(i
, argv
+ 1) {
442 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
444 if (streq(argv
[0], "users-in-group")) {
445 r
= membershipdb_by_group(*i
, arg_userdb_flags
, &iterator
);
447 return log_error_errno(r
, "Failed to enumerate users in group: %m");
448 } else if (streq(argv
[0], "groups-of-user")) {
449 r
= membershipdb_by_user(*i
, arg_userdb_flags
, &iterator
);
451 return log_error_errno(r
, "Failed to enumerate groups of user: %m");
453 assert_not_reached();
456 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
458 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
462 return log_error_errno(r
, "Selected membership database service is not available for this request.");
464 return log_error_errno(r
, "Failed acquire next membership: %m");
466 r
= show_membership(user
, group
, table
);
472 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
474 r
= membershipdb_all(arg_userdb_flags
, &iterator
);
476 log_debug_errno(r
, "No entries found. (Didn't check via Varlink.)");
477 else if (r
== -ESRCH
)
478 log_debug_errno(r
, "No entries found.");
480 return log_error_errno(r
, "Failed to enumerate memberships: %m");
483 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
485 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
489 return log_error_errno(r
, "Selected membership database service is not available for this request.");
491 return log_error_errno(r
, "Failed acquire next membership: %m");
493 r
= show_membership(user
, group
, table
);
501 if (table_get_rows(table
) > 1) {
502 r
= table_print_with_pager(table
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
504 return table_log_print_error(r
);
508 if (table_get_rows(table
) > 1)
509 printf("\n%zu memberships listed.\n", table_get_rows(table
) - 1);
511 printf("No memberships.\n");
518 static int display_services(int argc
, char *argv
[], void *userdata
) {
519 _cleanup_(table_unrefp
) Table
*t
= NULL
;
520 _cleanup_(closedirp
) DIR *d
= NULL
;
523 d
= opendir("/run/systemd/userdb/");
525 if (errno
== ENOENT
) {
526 log_info("No services.");
530 return log_error_errno(errno
, "Failed to open /run/systemd/userdb/: %m");
533 t
= table_new("service", "listening");
537 (void) table_set_sort(t
, (size_t) 0);
539 FOREACH_DIRENT(de
, d
, return -errno
) {
540 _cleanup_free_
char *j
= NULL
, *no
= NULL
;
541 union sockaddr_union sockaddr
;
542 socklen_t sockaddr_len
;
543 _cleanup_close_
int fd
= -1;
545 j
= path_join("/run/systemd/userdb/", de
->d_name
);
549 r
= sockaddr_un_set_path(&sockaddr
.un
, j
);
551 return log_error_errno(r
, "Path %s does not fit in AF_UNIX socket address: %m", j
);
554 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
556 return log_error_errno(r
, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m");
558 if (connect(fd
, &sockaddr
.sa
, sockaddr_len
) < 0) {
559 no
= strjoin("No (", errno_to_name(errno
), ")");
564 r
= table_add_many(t
,
565 TABLE_STRING
, de
->d_name
,
566 TABLE_STRING
, no
?: "yes",
567 TABLE_SET_COLOR
, no
? ansi_highlight_red() : ansi_highlight_green());
569 return table_log_add_error(r
);
572 if (table_get_rows(t
) > 1) {
573 r
= table_print_with_pager(t
, arg_json_format_flags
, arg_pager_flags
, arg_legend
);
575 return table_log_print_error(r
);
579 if (table_get_rows(t
) > 1)
580 printf("\n%zu services listed.\n", table_get_rows(t
) - 1);
582 printf("No services.\n");
588 static int ssh_authorized_keys(int argc
, char *argv
[], void *userdata
) {
589 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
590 char **chain_invocation
;
596 /* If --chain is specified, the rest of the command line is the chain command */
599 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
600 "No chain command line specified, refusing.");
602 /* Make similar restrictions on the chain command as OpenSSH itself makes on the primary command. */
603 if (!path_is_absolute(argv
[2]))
604 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
605 "Chain invocation of ssh-authorized-keys commands requires an absolute binary path argument.");
607 if (!path_is_normalized(argv
[2]))
608 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
609 "Chain invocation of ssh-authorized-keys commands requires an normalized binary path argument.");
611 chain_invocation
= argv
+ 2;
613 /* If --chain is not specified, then refuse any further arguments */
616 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Too many arguments.");
618 chain_invocation
= NULL
;
621 r
= userdb_by_name(argv
[1], arg_userdb_flags
, &ur
);
623 log_error_errno(r
, "User %s does not exist.", argv
[1]);
624 else if (r
== -EHOSTDOWN
)
625 log_error_errno(r
, "Selected user database service is not available for this request.");
626 else if (r
== -EINVAL
)
627 log_error_errno(r
, "Failed to find user %s: %m (Invalid user name?)", argv
[1]);
629 log_error_errno(r
, "Failed to find user %s: %m", argv
[1]);
631 if (strv_isempty(ur
->ssh_authorized_keys
))
632 log_debug("User record for %s has no public SSH keys.", argv
[1]);
636 STRV_FOREACH(i
, ur
->ssh_authorized_keys
)
640 if (ur
->incomplete
) {
642 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur
->user_name
);
646 if (chain_invocation
) {
648 _cleanup_free_
char *s
= NULL
;
650 s
= quote_command_line(chain_invocation
, SHELL_ESCAPE_EMPTY
);
654 log_debug("Chain invoking: %s", s
);
657 execv(chain_invocation
[0], chain_invocation
);
658 if (errno
== ENOENT
) /* Let's handle ENOENT gracefully */
659 log_warning_errno(errno
, "Chain executable '%s' does not exist, ignoring chain invocation.", chain_invocation
[0]);
661 log_error_errno(errno
, "Failed to invoke chain executable '%s': %m", chain_invocation
[0]);
670 static int help(int argc
, char *argv
[], void *userdata
) {
671 _cleanup_free_
char *link
= NULL
;
674 pager_open(arg_pager_flags
);
676 r
= terminal_urlify_man("userdbctl", "1", &link
);
680 printf("%s [OPTIONS...] COMMAND ...\n\n"
681 "%sShow user and group information.%s\n"
683 " user [USER…] Inspect user\n"
684 " group [GROUP…] Inspect group\n"
685 " users-in-group [GROUP…] Show users that are members of specified group(s)\n"
686 " groups-of-user [USER…] Show groups the specified user(s) is a member of\n"
687 " services Show enabled database services\n"
688 " ssh-authorized-keys USER Show SSH authorized keys for user\n"
690 " -h --help Show this help\n"
691 " --version Show package version\n"
692 " --no-pager Do not pipe output into a pager\n"
693 " --no-legend Do not show the headers and footers\n"
694 " --output=MODE Select output mode (classic, friendly, table, json)\n"
695 " -j Equivalent to --output=json\n"
696 " -s --service=SERVICE[:SERVICE…]\n"
697 " Query the specified service\n"
698 " --with-nss=BOOL Control whether to include glibc NSS data\n"
699 " -N Do not synthesize or include glibc NSS data\n"
700 " (Same as --synthesize=no --with-nss=no)\n"
701 " --synthesize=BOOL Synthesize root/nobody user\n"
702 " --with-dropin=BOOL Control whether to include drop-in records\n"
703 " --with-varlink=BOOL Control whether to talk to services at all\n"
704 " --multiplexer=BOOL Control whether to use the multiplexer\n"
705 " --json=pretty|short JSON output mode\n"
706 " --chain Chain another command\n"
707 "\nSee the %s for details.\n",
708 program_invocation_short_name
,
716 static int parse_argv(int argc
, char *argv
[]) {
732 static const struct option options
[] = {
733 { "help", no_argument
, NULL
, 'h' },
734 { "version", no_argument
, NULL
, ARG_VERSION
},
735 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
736 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
737 { "output", required_argument
, NULL
, ARG_OUTPUT
},
738 { "service", required_argument
, NULL
, 's' },
739 { "with-nss", required_argument
, NULL
, ARG_WITH_NSS
},
740 { "with-dropin", required_argument
, NULL
, ARG_WITH_DROPIN
},
741 { "with-varlink", required_argument
, NULL
, ARG_WITH_VARLINK
},
742 { "synthesize", required_argument
, NULL
, ARG_SYNTHESIZE
},
743 { "multiplexer", required_argument
, NULL
, ARG_MULTIPLEXER
},
744 { "json", required_argument
, NULL
, ARG_JSON
},
745 { "chain", no_argument
, NULL
, ARG_CHAIN
},
755 /* We are going to update this environment variable with our own, hence let's first read what is already set */
756 e
= getenv("SYSTEMD_ONLY_USERDB");
760 l
= strv_split(e
, ":");
764 strv_free(arg_services
);
771 c
= getopt_long(argc
, argv
,
772 arg_chain
? "+hjs:N" : "hjs:N", /* When --chain was used disable parsing of further switches */
780 return help(0, NULL
, NULL
);
786 arg_pager_flags
|= PAGER_DISABLE
;
795 arg_output
= _OUTPUT_INVALID
;
796 else if (streq(optarg
, "classic"))
797 arg_output
= OUTPUT_CLASSIC
;
798 else if (streq(optarg
, "friendly"))
799 arg_output
= OUTPUT_FRIENDLY
;
800 else if (streq(optarg
, "json"))
801 arg_output
= OUTPUT_JSON
;
802 else if (streq(optarg
, "table"))
803 arg_output
= OUTPUT_TABLE
;
804 else if (streq(optarg
, "help")) {
811 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid --output= mode: %s", optarg
);
813 arg_json_format_flags
= arg_output
== OUTPUT_JSON
? JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
: JSON_FORMAT_OFF
;
817 r
= parse_json_argument(optarg
, &arg_json_format_flags
);
821 arg_output
= FLAGS_SET(arg_json_format_flags
, JSON_FORMAT_OFF
) ? _OUTPUT_INVALID
: OUTPUT_JSON
;
825 arg_json_format_flags
= JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
;
826 arg_output
= OUTPUT_JSON
;
831 arg_services
= strv_free(arg_services
);
833 _cleanup_strv_free_
char **l
= NULL
;
835 l
= strv_split(optarg
, ":");
839 r
= strv_extend_strv(&arg_services
, l
, true);
847 arg_userdb_flags
|= USERDB_EXCLUDE_NSS
|USERDB_DONT_SYNTHESIZE
;
851 r
= parse_boolean_argument("--with-nss=", optarg
, NULL
);
855 SET_FLAG(arg_userdb_flags
, USERDB_EXCLUDE_NSS
, !r
);
858 case ARG_WITH_DROPIN
:
859 r
= parse_boolean_argument("--with-dropin=", optarg
, NULL
);
863 SET_FLAG(arg_userdb_flags
, USERDB_EXCLUDE_DROPIN
, !r
);
866 case ARG_WITH_VARLINK
:
867 r
= parse_boolean_argument("--with-varlink=", optarg
, NULL
);
871 SET_FLAG(arg_userdb_flags
, USERDB_EXCLUDE_VARLINK
, !r
);
875 r
= parse_boolean_argument("--synthesize=", optarg
, NULL
);
879 SET_FLAG(arg_userdb_flags
, USERDB_DONT_SYNTHESIZE
, !r
);
882 case ARG_MULTIPLEXER
:
883 r
= parse_boolean_argument("--multiplexer=", optarg
, NULL
);
887 SET_FLAG(arg_userdb_flags
, USERDB_AVOID_MULTIPLEXER
, !r
);
898 assert_not_reached();
905 static int run(int argc
, char *argv
[]) {
906 static const Verb verbs
[] = {
907 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
908 { "user", VERB_ANY
, VERB_ANY
, VERB_DEFAULT
, display_user
},
909 { "group", VERB_ANY
, VERB_ANY
, 0, display_group
},
910 { "users-in-group", VERB_ANY
, VERB_ANY
, 0, display_memberships
},
911 { "groups-of-user", VERB_ANY
, VERB_ANY
, 0, display_memberships
},
912 { "services", VERB_ANY
, 1, 0, display_services
},
914 /* This one is a helper for sshd_config's AuthorizedKeysCommand= setting, it's not a
915 * user-facing verb and thus should not appear in man pages or --help texts. */
916 { "ssh-authorized-keys", 2, VERB_ANY
, 0, ssh_authorized_keys
},
924 r
= parse_argv(argc
, argv
);
929 _cleanup_free_
char *e
= NULL
;
931 e
= strv_join(arg_services
, ":");
935 if (setenv("SYSTEMD_ONLY_USERDB", e
, true) < 0)
936 return log_error_errno(r
, "Failed to set $SYSTEMD_ONLY_USERDB: %m");
938 log_info("Enabled services: %s", e
);
940 assert_se(unsetenv("SYSTEMD_ONLY_USERDB") == 0);
942 return dispatch_verb(argc
, argv
, verbs
, NULL
);
945 DEFINE_MAIN_FUNCTION(run
);