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 log_error_errno(r
, "Failed to add row to table: %m");
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 log_error_errno(r
, "Failed to show table: %m");
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_INT
, (int) group_record_disposition(gr
));
237 return log_error_errno(r
, "Failed to add row to table: %m");
242 assert_not_reached("Unexpected disply mode");
249 static int display_group(int argc
, char *argv
[], void *userdata
) {
250 _cleanup_(table_unrefp
) Table
*table
= NULL
;
251 bool draw_separator
= false;
255 arg_output
= argc
> 1 ? OUTPUT_FRIENDLY
: OUTPUT_TABLE
;
257 if (arg_output
== OUTPUT_TABLE
) {
258 table
= table_new("name", "disposition", "gid", "disposition-numeric");
262 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 2), 100);
263 (void) table_set_sort(table
, (size_t) 3, (size_t) 2, (size_t) -1);
264 (void) table_set_display(table
, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) -1);
270 STRV_FOREACH(i
, argv
+ 1) {
271 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
274 if (parse_gid(*i
, &gid
) >= 0)
275 r
= groupdb_by_gid(gid
, arg_userdb_flags
, &gr
);
277 r
= groupdb_by_name(*i
, arg_userdb_flags
, &gr
);
280 log_error_errno(r
, "Group %s does not exist.", *i
);
281 else if (r
== -EHOSTDOWN
)
282 log_error_errno(r
, "Selected group database service is not available for this request.");
284 log_error_errno(r
, "Failed to find group %s: %m", *i
);
289 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
292 r
= show_group(gr
, table
);
296 draw_separator
= true;
301 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
303 r
= groupdb_all(arg_userdb_flags
, &iterator
);
305 return log_error_errno(r
, "Failed to enumerate groups: %m");
308 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
310 r
= groupdb_iterator_get(iterator
, &gr
);
314 return log_error_errno(r
, "Selected group database service is not available for this request.");
316 return log_error_errno(r
, "Failed acquire next group: %m");
318 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
321 r
= show_group(gr
, table
);
325 draw_separator
= true;
331 r
= table_print(table
, NULL
);
333 return log_error_errno(r
, "Failed to show table: %m");
339 static int show_membership(const char *user
, const char *group
, Table
*table
) {
345 switch (arg_output
) {
348 /* Strictly speaking there's no 'classic' output for this concept, but let's output it in
349 * similar style to the classic output for user/group info */
351 printf("%s:%s\n", user
, group
);
355 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
357 r
= json_build(&v
, JSON_BUILD_OBJECT(
358 JSON_BUILD_PAIR("user", JSON_BUILD_STRING(user
)),
359 JSON_BUILD_PAIR("group", JSON_BUILD_STRING(group
))));
361 return log_error_errno(r
, "Failed to build JSON object: %m");
363 json_variant_dump(v
, JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
, NULL
, NULL
);
367 case OUTPUT_FRIENDLY
:
368 /* Hmm, this is not particularly friendly, but not sure how we could do this better */
369 printf("%s: %s\n", group
, user
);
378 TABLE_STRING
, group
);
380 return log_error_errno(r
, "Failed to add row to table: %m");
385 assert_not_reached("Unexpected output mode");
391 static int display_memberships(int argc
, char *argv
[], void *userdata
) {
392 _cleanup_(table_unrefp
) Table
*table
= NULL
;
396 arg_output
= OUTPUT_TABLE
;
398 if (arg_output
== OUTPUT_TABLE
) {
399 table
= table_new("user", "group");
403 (void) table_set_sort(table
, (size_t) 0, (size_t) 1, (size_t) -1);
409 STRV_FOREACH(i
, argv
+ 1) {
410 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
412 if (streq(argv
[0], "users-in-group")) {
413 r
= membershipdb_by_group(*i
, arg_userdb_flags
, &iterator
);
415 return log_error_errno(r
, "Failed to enumerate users in group: %m");
416 } else if (streq(argv
[0], "groups-of-user")) {
417 r
= membershipdb_by_user(*i
, arg_userdb_flags
, &iterator
);
419 return log_error_errno(r
, "Failed to enumerate groups of user: %m");
421 assert_not_reached("Unexpected verb");
424 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
426 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
430 return log_error_errno(r
, "Selected membership database service is not available for this request.");
432 return log_error_errno(r
, "Failed acquire next membership: %m");
434 r
= show_membership(user
, group
, table
);
440 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
442 r
= membershipdb_all(arg_userdb_flags
, &iterator
);
444 return log_error_errno(r
, "Failed to enumerate memberships: %m");
447 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
449 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
453 return log_error_errno(r
, "Selected membership database service is not available for this request.");
455 return log_error_errno(r
, "Failed acquire next membership: %m");
457 r
= show_membership(user
, group
, table
);
464 r
= table_print(table
, NULL
);
466 return log_error_errno(r
, "Failed to show table: %m");
472 static int display_services(int argc
, char *argv
[], void *userdata
) {
473 _cleanup_(table_unrefp
) Table
*t
= NULL
;
474 _cleanup_(closedirp
) DIR *d
= NULL
;
478 d
= opendir("/run/systemd/userdb/");
480 if (errno
== ENOENT
) {
481 log_info("No services.");
485 return log_error_errno(errno
, "Failed to open /run/systemd/userdb/: %m");
488 t
= table_new("service", "listening");
492 (void) table_set_sort(t
, (size_t) 0, (size_t) -1);
494 FOREACH_DIRENT(de
, d
, return -errno
) {
495 _cleanup_free_
char *j
= NULL
, *no
= NULL
;
496 union sockaddr_union sockaddr
;
497 _cleanup_close_
int fd
= -1;
499 j
= path_join("/run/systemd/userdb/", de
->d_name
);
503 r
= sockaddr_un_set_path(&sockaddr
.un
, j
);
505 return log_error_errno(r
, "Path %s does not fit in AF_UNIX socket address: %m", j
);
507 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
509 return log_error_errno(r
, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m");
511 if (connect(fd
, &sockaddr
.un
, SOCKADDR_UN_LEN(sockaddr
.un
)) < 0) {
512 no
= strjoin("No (", errno_to_name(errno
), ")");
517 r
= table_add_many(t
,
518 TABLE_STRING
, de
->d_name
,
519 TABLE_STRING
, no
?: "yes",
520 TABLE_SET_COLOR
, no
? ansi_highlight_red() : ansi_highlight_green());
522 return log_error_errno(r
, "Failed to add table row: %m");
525 if (table_get_rows(t
) <= 0) {
526 log_info("No services.");
530 if (arg_output
== OUTPUT_JSON
)
531 table_print_json(t
, NULL
, JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
);
533 table_print(t
, NULL
);
538 static int ssh_authorized_keys(int argc
, char *argv
[], void *userdata
) {
539 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
542 if (!valid_user_group_name(argv
[1]))
543 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid user name '%s'.", argv
[1]);
545 r
= userdb_by_name(argv
[1], arg_userdb_flags
, &ur
);
547 log_error_errno(r
, "User %s does not exist.", argv
[1]);
548 else if (r
== -EHOSTDOWN
)
549 log_error_errno(r
, "Selected user database service is not available for this request.");
551 log_error_errno(r
, "Failed to find user %s: %m", argv
[1]);
553 if (strv_isempty(ur
->ssh_authorized_keys
))
554 log_debug("User record for %s has no public SSH keys.", argv
[1]);
558 STRV_FOREACH(i
, ur
->ssh_authorized_keys
)
562 if (ur
->incomplete
) {
564 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur
->user_name
);
570 static int help(int argc
, char *argv
[], void *userdata
) {
571 _cleanup_free_
char *link
= NULL
;
574 (void) pager_open(arg_pager_flags
);
576 r
= terminal_urlify_man("userdbctl", "1", &link
);
580 printf("%s [OPTIONS...] COMMAND ...\n\n"
581 "%sShow user and group information.%s\n"
583 " user [USER…] Inspect user\n"
584 " group [GROUP…] Inspect group\n"
585 " users-in-group [GROUP…] Show users that are members of specified group(s)\n"
586 " groups-of-user [USER…] Show groups the specified user(s) is a member of\n"
587 " services Show enabled database services\n"
589 " -h --help Show this help\n"
590 " --version Show package version\n"
591 " --no-pager Do not pipe output into a pager\n"
592 " --no-legend Do not show the headers and footers\n"
593 " --output=MODE Select output mode (classic, friendly, table, json)\n"
594 " -j Equivalent to --output=json\n"
595 " -s --service=SERVICE[:SERVICE…]\n"
596 " Query the specified service\n"
597 " --with-nss=BOOL Control whether to include glibc NSS data\n"
598 " -N Disable inclusion of glibc NSS data and disable synthesizing\n"
599 " (Same as --with-nss=no --synthesize=no)\n"
600 " --synthesize=BOOL Synthesize root/nobody user\n"
601 "\nSee the %s for details.\n"
602 , program_invocation_short_name
603 , ansi_highlight(), ansi_normal()
610 static int parse_argv(int argc
, char *argv
[]) {
621 static const struct option options
[] = {
622 { "help", no_argument
, NULL
, 'h' },
623 { "version", no_argument
, NULL
, ARG_VERSION
},
624 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
625 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
626 { "output", required_argument
, NULL
, ARG_OUTPUT
},
627 { "service", required_argument
, NULL
, 's' },
628 { "with-nss", required_argument
, NULL
, ARG_WITH_NSS
},
629 { "synthesize", required_argument
, NULL
, ARG_SYNTHESIZE
},
639 /* We are going to update this environment variable with our own, hence let's first read what is already set */
640 e
= getenv("SYSTEMD_ONLY_USERDB");
644 l
= strv_split(e
, ":");
648 strv_free(arg_services
);
655 c
= getopt_long(argc
, argv
, "hjs:N", options
, NULL
);
662 return help(0, NULL
, NULL
);
668 arg_pager_flags
|= PAGER_DISABLE
;
676 if (streq(optarg
, "classic"))
677 arg_output
= OUTPUT_CLASSIC
;
678 else if (streq(optarg
, "friendly"))
679 arg_output
= OUTPUT_FRIENDLY
;
680 else if (streq(optarg
, "json"))
681 arg_output
= OUTPUT_JSON
;
682 else if (streq(optarg
, "table"))
683 arg_output
= OUTPUT_TABLE
;
684 else if (streq(optarg
, "help")) {
690 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
), "Invalid --output= mode: %s", optarg
);
695 arg_output
= OUTPUT_JSON
;
700 arg_services
= strv_free(arg_services
);
704 l
= strv_split(optarg
, ":");
708 r
= strv_extend_strv(&arg_services
, l
, true);
716 arg_userdb_flags
|= USERDB_AVOID_NSS
|USERDB_DONT_SYNTHESIZE
;
720 r
= parse_boolean(optarg
);
722 return log_error_errno(r
, "Failed to parse --with-nss= parameter: %s", optarg
);
724 SET_FLAG(arg_userdb_flags
, USERDB_AVOID_NSS
, !r
);
728 r
= parse_boolean(optarg
);
730 return log_error_errno(r
, "Failed to parse --synthesize= parameter: %s", optarg
);
732 SET_FLAG(arg_userdb_flags
, USERDB_DONT_SYNTHESIZE
, !r
);
739 assert_not_reached("Unhandled option");
746 static int run(int argc
, char *argv
[]) {
747 static const Verb verbs
[] = {
748 { "help", VERB_ANY
, VERB_ANY
, 0, help
},
749 { "user", VERB_ANY
, VERB_ANY
, VERB_DEFAULT
, display_user
},
750 { "group", VERB_ANY
, VERB_ANY
, 0, display_group
},
751 { "users-in-group", VERB_ANY
, VERB_ANY
, 0, display_memberships
},
752 { "groups-of-user", VERB_ANY
, VERB_ANY
, 0, display_memberships
},
753 { "services", VERB_ANY
, 1, 0, display_services
},
755 /* This one is a helper for sshd_config's AuthorizedKeysCommand= setting, it's not a
756 * user-facing verb and thus should not appear in man pages or --help texts. */
757 { "ssh-authorized-keys", 2, 2, 0, ssh_authorized_keys
},
763 log_show_color(true);
764 log_parse_environment();
767 r
= parse_argv(argc
, argv
);
772 _cleanup_free_
char *e
= NULL
;
774 e
= strv_join(arg_services
, ":");
778 if (setenv("SYSTEMD_ONLY_USERDB", e
, true) < 0)
779 return log_error_errno(r
, "Failed to set $SYSTEMD_ONLY_USERDB: %m");
781 log_info("Enabled services: %s", e
);
783 if (unsetenv("SYSTEMD_ONLY_USERDB") < 0)
784 return log_error_errno(r
, "Failed to unset $SYSTEMD_ONLY_USERDB: %m");
787 return dispatch_verb(argc
, argv
, verbs
, NULL
);
790 DEFINE_MAIN_FUNCTION(run
);