1 /* SPDX-License-Identifier: LGPL-2.1+ */
6 #include "dirent-util.h"
7 #include "errno-list.h"
9 #include "format-table.h"
10 #include "format-util.h"
11 #include "group-record-show.h"
12 #include "main-func.h"
14 #include "parse-util.h"
15 #include "pretty-print.h"
16 #include "socket-util.h"
18 #include "terminal-util.h"
19 #include "user-record-show.h"
20 #include "user-util.h"
30 } arg_output
= _OUTPUT_INVALID
;
32 static PagerFlags arg_pager_flags
= 0;
33 static bool arg_legend
= true;
34 static char** arg_services
= NULL
;
35 static UserDBFlags arg_userdb_flags
= 0;
37 STATIC_DESTRUCTOR_REGISTER(arg_services
, strv_freep
);
39 static int show_user(UserRecord
*ur
, Table
*table
) {
47 if (!uid_is_valid(ur
->uid
))
50 printf("%s:x:" UID_FMT
":" GID_FMT
":%s:%s:%s\n",
54 strempty(user_record_real_name(ur
)),
55 user_record_home_directory(ur
),
56 user_record_shell(ur
));
61 json_variant_dump(ur
->json
, JSON_FORMAT_COLOR_AUTO
|JSON_FORMAT_PRETTY
, NULL
, 0);
65 user_record_show(ur
, true);
69 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur
->user_name
);
79 TABLE_STRING
, ur
->user_name
,
80 TABLE_STRING
, user_disposition_to_string(user_record_disposition(ur
)),
82 TABLE_GID
, user_record_gid(ur
),
83 TABLE_STRING
, empty_to_null(ur
->real_name
),
84 TABLE_STRING
, user_record_home_directory(ur
),
85 TABLE_STRING
, user_record_shell(ur
),
86 TABLE_INT
, (int) user_record_disposition(ur
));
88 return table_log_add_error(r
);
93 assert_not_reached("Unexpected output mode");
99 static int display_user(int argc
, char *argv
[], void *userdata
) {
100 _cleanup_(table_unrefp
) Table
*table
= NULL
;
101 bool draw_separator
= false;
105 arg_output
= argc
> 1 ? OUTPUT_FRIENDLY
: OUTPUT_TABLE
;
107 if (arg_output
== OUTPUT_TABLE
) {
108 table
= table_new("name", "disposition", "uid", "gid", "realname", "home", "shell", "disposition-numeric");
112 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 2), 100);
113 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 3), 100);
114 (void) table_set_empty_string(table
, "-");
115 (void) table_set_sort(table
, (size_t) 7, (size_t) 2, (size_t) -1);
116 (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) -1);
122 STRV_FOREACH(i
, argv
+ 1) {
123 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
126 if (parse_uid(*i
, &uid
) >= 0)
127 r
= userdb_by_uid(uid
, arg_userdb_flags
, &ur
);
129 r
= userdb_by_name(*i
, arg_userdb_flags
, &ur
);
132 log_error_errno(r
, "User %s does not exist.", *i
);
133 else if (r
== -EHOSTDOWN
)
134 log_error_errno(r
, "Selected user database service is not available for this request.");
136 log_error_errno(r
, "Failed to find user %s: %m", *i
);
141 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
144 r
= show_user(ur
, table
);
148 draw_separator
= true;
152 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
154 r
= userdb_all(arg_userdb_flags
, &iterator
);
156 return log_error_errno(r
, "Failed to enumerate users: %m");
159 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
161 r
= userdb_iterator_get(iterator
, &ur
);
165 return log_error_errno(r
, "Selected user database service is not available for this request.");
167 return log_error_errno(r
, "Failed acquire next user: %m");
169 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
172 r
= show_user(ur
, table
);
176 draw_separator
= true;
181 r
= table_print(table
, NULL
);
183 return table_log_print_error(r
);
189 static int show_group(GroupRecord
*gr
, Table
*table
) {
194 switch (arg_output
) {
196 case OUTPUT_CLASSIC
: {
197 _cleanup_free_
char *m
= NULL
;
199 if (!gid_is_valid(gr
->gid
))
202 m
= strv_join(gr
->members
, ",");
206 printf("%s:x:" GID_FMT
":%s\n",
214 json_variant_dump(gr
->json
, JSON_FORMAT_COLOR_AUTO
|JSON_FORMAT_PRETTY
, NULL
, 0);
217 case OUTPUT_FRIENDLY
:
218 group_record_show(gr
, true);
220 if (gr
->incomplete
) {
222 log_warning("Warning: lacking rights to acquire privileged fields of group record of '%s', output incomplete.", gr
->group_name
);
232 TABLE_STRING
, gr
->group_name
,
233 TABLE_STRING
, user_disposition_to_string(group_record_disposition(gr
)),
235 TABLE_STRING
, gr
->description
,
236 TABLE_INT
, (int) group_record_disposition(gr
));
238 return table_log_add_error(r
);
243 assert_not_reached("Unexpected display mode");
250 static int display_group(int argc
, char *argv
[], void *userdata
) {
251 _cleanup_(table_unrefp
) Table
*table
= NULL
;
252 bool draw_separator
= false;
256 arg_output
= argc
> 1 ? OUTPUT_FRIENDLY
: OUTPUT_TABLE
;
258 if (arg_output
== OUTPUT_TABLE
) {
259 table
= table_new("name", "disposition", "gid", "description", "disposition-numeric");
263 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 2), 100);
264 (void) table_set_empty_string(table
, "-");
265 (void) table_set_sort(table
, (size_t) 3, (size_t) 2, (size_t) -1);
266 (void) table_set_display(table
, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) -1);
272 STRV_FOREACH(i
, argv
+ 1) {
273 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
276 if (parse_gid(*i
, &gid
) >= 0)
277 r
= groupdb_by_gid(gid
, arg_userdb_flags
, &gr
);
279 r
= groupdb_by_name(*i
, arg_userdb_flags
, &gr
);
282 log_error_errno(r
, "Group %s does not exist.", *i
);
283 else if (r
== -EHOSTDOWN
)
284 log_error_errno(r
, "Selected group database service is not available for this request.");
286 log_error_errno(r
, "Failed to find group %s: %m", *i
);
291 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
294 r
= show_group(gr
, table
);
298 draw_separator
= true;
303 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
305 r
= groupdb_all(arg_userdb_flags
, &iterator
);
307 return log_error_errno(r
, "Failed to enumerate groups: %m");
310 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
312 r
= groupdb_iterator_get(iterator
, &gr
);
316 return log_error_errno(r
, "Selected group database service is not available for this request.");
318 return log_error_errno(r
, "Failed acquire next group: %m");
320 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
323 r
= show_group(gr
, table
);
327 draw_separator
= true;
333 r
= table_print(table
, NULL
);
335 return table_log_print_error(r
);
341 static int show_membership(const char *user
, const char *group
, Table
*table
) {
347 switch (arg_output
) {
350 /* Strictly speaking there's no 'classic' output for this concept, but let's output it in
351 * similar style to the classic output for user/group info */
353 printf("%s:%s\n", user
, group
);
357 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
359 r
= json_build(&v
, JSON_BUILD_OBJECT(
360 JSON_BUILD_PAIR("user", JSON_BUILD_STRING(user
)),
361 JSON_BUILD_PAIR("group", JSON_BUILD_STRING(group
))));
363 return log_error_errno(r
, "Failed to build JSON object: %m");
365 json_variant_dump(v
, JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
, NULL
, NULL
);
369 case OUTPUT_FRIENDLY
:
370 /* Hmm, this is not particularly friendly, but not sure how we could do this better */
371 printf("%s: %s\n", group
, user
);
380 TABLE_STRING
, group
);
382 return table_log_add_error(r
);
387 assert_not_reached("Unexpected output mode");
393 static int display_memberships(int argc
, char *argv
[], void *userdata
) {
394 _cleanup_(table_unrefp
) Table
*table
= NULL
;
398 arg_output
= OUTPUT_TABLE
;
400 if (arg_output
== OUTPUT_TABLE
) {
401 table
= table_new("user", "group");
405 (void) table_set_sort(table
, (size_t) 0, (size_t) 1, (size_t) -1);
411 STRV_FOREACH(i
, argv
+ 1) {
412 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
414 if (streq(argv
[0], "users-in-group")) {
415 r
= membershipdb_by_group(*i
, arg_userdb_flags
, &iterator
);
417 return log_error_errno(r
, "Failed to enumerate users in group: %m");
418 } else if (streq(argv
[0], "groups-of-user")) {
419 r
= membershipdb_by_user(*i
, arg_userdb_flags
, &iterator
);
421 return log_error_errno(r
, "Failed to enumerate groups of user: %m");
423 assert_not_reached("Unexpected verb");
426 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
428 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
432 return log_error_errno(r
, "Selected membership database service is not available for this request.");
434 return log_error_errno(r
, "Failed acquire next membership: %m");
436 r
= show_membership(user
, group
, table
);
442 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
444 r
= membershipdb_all(arg_userdb_flags
, &iterator
);
446 return log_error_errno(r
, "Failed to enumerate memberships: %m");
449 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
451 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
455 return log_error_errno(r
, "Selected membership database service is not available for this request.");
457 return log_error_errno(r
, "Failed acquire next membership: %m");
459 r
= show_membership(user
, group
, table
);
466 r
= table_print(table
, NULL
);
468 return table_log_print_error(r
);
474 static int display_services(int argc
, char *argv
[], void *userdata
) {
475 _cleanup_(table_unrefp
) Table
*t
= NULL
;
476 _cleanup_(closedirp
) DIR *d
= NULL
;
480 d
= opendir("/run/systemd/userdb/");
482 if (errno
== ENOENT
) {
483 log_info("No services.");
487 return log_error_errno(errno
, "Failed to open /run/systemd/userdb/: %m");
490 t
= table_new("service", "listening");
494 (void) table_set_sort(t
, (size_t) 0, (size_t) -1);
496 FOREACH_DIRENT(de
, d
, return -errno
) {
497 _cleanup_free_
char *j
= NULL
, *no
= NULL
;
498 union sockaddr_union sockaddr
;
499 socklen_t sockaddr_len
;
500 _cleanup_close_
int fd
= -1;
502 j
= path_join("/run/systemd/userdb/", de
->d_name
);
506 r
= sockaddr_un_set_path(&sockaddr
.un
, j
);
508 return log_error_errno(r
, "Path %s does not fit in AF_UNIX socket address: %m", j
);
511 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
513 return log_error_errno(r
, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m");
515 if (connect(fd
, &sockaddr
.un
, sockaddr_len
) < 0) {
516 no
= strjoin("No (", errno_to_name(errno
), ")");
521 r
= table_add_many(t
,
522 TABLE_STRING
, de
->d_name
,
523 TABLE_STRING
, no
?: "yes",
524 TABLE_SET_COLOR
, no
? ansi_highlight_red() : ansi_highlight_green());
526 return table_log_add_error(r
);
529 if (table_get_rows(t
) <= 0) {
530 log_info("No services.");
534 if (arg_output
== OUTPUT_JSON
)
535 table_print_json(t
, NULL
, JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
);
537 table_print(t
, NULL
);
542 static int ssh_authorized_keys(int argc
, char *argv
[], void *userdata
) {
543 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
546 r
= userdb_by_name(argv
[1], arg_userdb_flags
, &ur
);
548 return log_error_errno(r
, "User %s does not exist.", argv
[1]);
549 else if (r
== -EHOSTDOWN
)
550 return log_error_errno(r
, "Selected user database service is not available for this request.");
551 else if (r
== -EINVAL
)
552 return log_error_errno(r
, "Failed to find user %s: %m (Invalid user name?)", argv
[1]);
554 return log_error_errno(r
, "Failed to find user %s: %m", argv
[1]);
556 if (strv_isempty(ur
->ssh_authorized_keys
))
557 log_debug("User record for %s has no public SSH keys.", argv
[1]);
561 STRV_FOREACH(i
, ur
->ssh_authorized_keys
)
565 if (ur
->incomplete
) {
567 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur
->user_name
);
573 static int help(int argc
, char *argv
[], void *userdata
) {
574 _cleanup_free_
char *link
= NULL
;
577 (void) pager_open(arg_pager_flags
);
579 r
= terminal_urlify_man("userdbctl", "1", &link
);
583 printf("%s [OPTIONS...] COMMAND ...\n\n"
584 "%sShow user and group information.%s\n"
586 " user [USER…] Inspect user\n"
587 " group [GROUP…] Inspect group\n"
588 " users-in-group [GROUP…] Show users that are members of specified group(s)\n"
589 " groups-of-user [USER…] Show groups the specified user(s) is a member of\n"
590 " services Show enabled database services\n"
592 " -h --help Show this help\n"
593 " --version Show package version\n"
594 " --no-pager Do not pipe output into a pager\n"
595 " --no-legend Do not show the headers and footers\n"
596 " --output=MODE Select output mode (classic, friendly, table, json)\n"
597 " -j Equivalent to --output=json\n"
598 " -s --service=SERVICE[:SERVICE…]\n"
599 " Query the specified service\n"
600 " --with-nss=BOOL Control whether to include glibc NSS data\n"
601 " -N Do not synthesize or include glibc NSS data\n"
602 " (Same as --synthesize=no --with-nss=no)\n"
603 " --synthesize=BOOL Synthesize root/nobody user\n"
604 "\nSee the %s for details.\n"
605 , program_invocation_short_name
606 , ansi_highlight(), ansi_normal()
613 static int parse_argv(int argc
, char *argv
[]) {
624 static const struct option options
[] = {
625 { "help", no_argument
, NULL
, 'h' },
626 { "version", no_argument
, NULL
, ARG_VERSION
},
627 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
628 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
629 { "output", required_argument
, NULL
, ARG_OUTPUT
},
630 { "service", required_argument
, NULL
, 's' },
631 { "with-nss", required_argument
, NULL
, ARG_WITH_NSS
},
632 { "synthesize", required_argument
, NULL
, ARG_SYNTHESIZE
},
642 /* We are going to update this environment variable with our own, hence let's first read what is already set */
643 e
= getenv("SYSTEMD_ONLY_USERDB");
647 l
= strv_split(e
, ":");
651 strv_free(arg_services
);
658 c
= getopt_long(argc
, argv
, "hjs:N", options
, NULL
);
665 return help(0, NULL
, NULL
);
671 arg_pager_flags
|= PAGER_DISABLE
;
679 if (streq(optarg
, "classic"))
680 arg_output
= OUTPUT_CLASSIC
;
681 else if (streq(optarg
, "friendly"))
682 arg_output
= OUTPUT_FRIENDLY
;
683 else if (streq(optarg
, "json"))
684 arg_output
= OUTPUT_JSON
;
685 else if (streq(optarg
, "table"))
686 arg_output
= OUTPUT_TABLE
;
687 else if (streq(optarg
, "help")) {
693 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid --output= mode: %s", optarg
);
698 arg_output
= OUTPUT_JSON
;
703 arg_services
= strv_free(arg_services
);
705 _cleanup_strv_free_
char **l
= NULL
;
707 l
= strv_split(optarg
, ":");
711 r
= strv_extend_strv(&arg_services
, l
, true);
719 arg_userdb_flags
|= USERDB_AVOID_NSS
|USERDB_DONT_SYNTHESIZE
;
723 r
= parse_boolean(optarg
);
725 return log_error_errno(r
, "Failed to parse --with-nss= parameter: %s", optarg
);
727 SET_FLAG(arg_userdb_flags
, USERDB_AVOID_NSS
, !r
);
731 r
= parse_boolean(optarg
);
733 return log_error_errno(r
, "Failed to parse --synthesize= parameter: %s", optarg
);
735 SET_FLAG(arg_userdb_flags
, USERDB_DONT_SYNTHESIZE
, !r
);
742 assert_not_reached("Unhandled option");
749 static int run(int argc
, char *argv
[]) {
750 static const Verb verbs
[] = {
751 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
752 { "user", VERB_ANY
, VERB_ANY
, VERB_DEFAULT
, display_user
},
753 { "group", VERB_ANY
, VERB_ANY
, 0, display_group
},
754 { "users-in-group", VERB_ANY
, VERB_ANY
, 0, display_memberships
},
755 { "groups-of-user", VERB_ANY
, VERB_ANY
, 0, display_memberships
},
756 { "services", VERB_ANY
, 1, 0, display_services
},
758 /* This one is a helper for sshd_config's AuthorizedKeysCommand= setting, it's not a
759 * user-facing verb and thus should not appear in man pages or --help texts. */
760 { "ssh-authorized-keys", 2, 2, 0, ssh_authorized_keys
},
768 r
= parse_argv(argc
, argv
);
773 _cleanup_free_
char *e
= NULL
;
775 e
= strv_join(arg_services
, ":");
779 if (setenv("SYSTEMD_ONLY_USERDB", e
, true) < 0)
780 return log_error_errno(r
, "Failed to set $SYSTEMD_ONLY_USERDB: %m");
782 log_info("Enabled services: %s", e
);
784 if (unsetenv("SYSTEMD_ONLY_USERDB") < 0)
785 return log_error_errno(r
, "Failed to unset $SYSTEMD_ONLY_USERDB: %m");
788 return dispatch_verb(argc
, argv
, verbs
, NULL
);
791 DEFINE_MAIN_FUNCTION(run
);