1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
6 #include "dirent-util.h"
7 #include "errno-list.h"
9 #include "format-table.h"
10 #include "format-util.h"
11 #include "main-func.h"
13 #include "parse-util.h"
14 #include "pretty-print.h"
15 #include "socket-util.h"
17 #include "terminal-util.h"
18 #include "user-record-show.h"
19 #include "user-util.h"
29 } arg_output
= _OUTPUT_INVALID
;
31 static PagerFlags arg_pager_flags
= 0;
32 static bool arg_legend
= true;
33 static char** arg_services
= NULL
;
34 static UserDBFlags arg_userdb_flags
= 0;
36 STATIC_DESTRUCTOR_REGISTER(arg_services
, strv_freep
);
38 static int show_user(UserRecord
*ur
, Table
*table
) {
46 if (!uid_is_valid(ur
->uid
))
49 printf("%s:x:" UID_FMT
":" GID_FMT
":%s:%s:%s\n",
53 strempty(user_record_real_name(ur
)),
54 user_record_home_directory(ur
),
55 user_record_shell(ur
));
60 json_variant_dump(ur
->json
, JSON_FORMAT_COLOR_AUTO
|JSON_FORMAT_PRETTY
, NULL
, 0);
64 user_record_show(ur
, true);
68 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur
->user_name
);
78 TABLE_STRING
, ur
->user_name
,
79 TABLE_STRING
, user_disposition_to_string(user_record_disposition(ur
)),
81 TABLE_GID
, user_record_gid(ur
),
82 TABLE_STRING
, empty_to_null(ur
->real_name
),
83 TABLE_STRING
, user_record_home_directory(ur
),
84 TABLE_STRING
, user_record_shell(ur
),
85 TABLE_INT
, (int) user_record_disposition(ur
));
87 return table_log_add_error(r
);
92 assert_not_reached("Unexpected output mode");
98 static int display_user(int argc
, char *argv
[], void *userdata
) {
99 _cleanup_(table_unrefp
) Table
*table
= NULL
;
100 bool draw_separator
= false;
104 arg_output
= argc
> 1 ? OUTPUT_FRIENDLY
: OUTPUT_TABLE
;
106 if (arg_output
== OUTPUT_TABLE
) {
107 table
= table_new("name", "disposition", "uid", "gid", "realname", "home", "shell", "disposition-numeric");
111 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 2), 100);
112 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 3), 100);
113 (void) table_set_empty_string(table
, "-");
114 (void) table_set_sort(table
, (size_t) 7, (size_t) 2, (size_t) -1);
115 (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);
121 STRV_FOREACH(i
, argv
+ 1) {
122 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
125 if (parse_uid(*i
, &uid
) >= 0)
126 r
= userdb_by_uid(uid
, arg_userdb_flags
, &ur
);
128 r
= userdb_by_name(*i
, arg_userdb_flags
, &ur
);
131 log_error_errno(r
, "User %s does not exist.", *i
);
132 else if (r
== -EHOSTDOWN
)
133 log_error_errno(r
, "Selected user database service is not available for this request.");
135 log_error_errno(r
, "Failed to find user %s: %m", *i
);
140 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
143 r
= show_user(ur
, table
);
147 draw_separator
= true;
151 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
153 r
= userdb_all(arg_userdb_flags
, &iterator
);
155 return log_error_errno(r
, "Failed to enumerate users: %m");
158 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
160 r
= userdb_iterator_get(iterator
, &ur
);
164 return log_error_errno(r
, "Selected user database service is not available for this request.");
166 return log_error_errno(r
, "Failed acquire next user: %m");
168 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
171 r
= show_user(ur
, table
);
175 draw_separator
= true;
180 r
= table_print(table
, NULL
);
182 return table_log_print_error(r
);
188 static int show_group(GroupRecord
*gr
, Table
*table
) {
193 switch (arg_output
) {
195 case OUTPUT_CLASSIC
: {
196 _cleanup_free_
char *m
= NULL
;
198 if (!gid_is_valid(gr
->gid
))
201 m
= strv_join(gr
->members
, ",");
205 printf("%s:x:" GID_FMT
":%s\n",
213 json_variant_dump(gr
->json
, JSON_FORMAT_COLOR_AUTO
|JSON_FORMAT_PRETTY
, NULL
, 0);
216 case OUTPUT_FRIENDLY
:
217 group_record_show(gr
, true);
219 if (gr
->incomplete
) {
221 log_warning("Warning: lacking rights to acquire privileged fields of group record of '%s', output incomplete.", gr
->group_name
);
231 TABLE_STRING
, gr
->group_name
,
232 TABLE_STRING
, user_disposition_to_string(group_record_disposition(gr
)),
234 TABLE_STRING
, gr
->description
,
235 TABLE_INT
, (int) group_record_disposition(gr
));
237 return table_log_add_error(r
);
242 assert_not_reached("Unexpected display 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", "description", "disposition-numeric");
262 (void) table_set_align_percent(table
, table_get_cell(table
, 0, 2), 100);
263 (void) table_set_empty_string(table
, "-");
264 (void) table_set_sort(table
, (size_t) 3, (size_t) 2, (size_t) -1);
265 (void) table_set_display(table
, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) -1);
271 STRV_FOREACH(i
, argv
+ 1) {
272 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
275 if (parse_gid(*i
, &gid
) >= 0)
276 r
= groupdb_by_gid(gid
, arg_userdb_flags
, &gr
);
278 r
= groupdb_by_name(*i
, arg_userdb_flags
, &gr
);
281 log_error_errno(r
, "Group %s does not exist.", *i
);
282 else if (r
== -EHOSTDOWN
)
283 log_error_errno(r
, "Selected group database service is not available for this request.");
285 log_error_errno(r
, "Failed to find group %s: %m", *i
);
290 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
293 r
= show_group(gr
, table
);
297 draw_separator
= true;
302 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
304 r
= groupdb_all(arg_userdb_flags
, &iterator
);
306 return log_error_errno(r
, "Failed to enumerate groups: %m");
309 _cleanup_(group_record_unrefp
) GroupRecord
*gr
= NULL
;
311 r
= groupdb_iterator_get(iterator
, &gr
);
315 return log_error_errno(r
, "Selected group database service is not available for this request.");
317 return log_error_errno(r
, "Failed acquire next group: %m");
319 if (draw_separator
&& arg_output
== OUTPUT_FRIENDLY
)
322 r
= show_group(gr
, table
);
326 draw_separator
= true;
332 r
= table_print(table
, NULL
);
334 return table_log_print_error(r
);
340 static int show_membership(const char *user
, const char *group
, Table
*table
) {
346 switch (arg_output
) {
349 /* Strictly speaking there's no 'classic' output for this concept, but let's output it in
350 * similar style to the classic output for user/group info */
352 printf("%s:%s\n", user
, group
);
356 _cleanup_(json_variant_unrefp
) JsonVariant
*v
= NULL
;
358 r
= json_build(&v
, JSON_BUILD_OBJECT(
359 JSON_BUILD_PAIR("user", JSON_BUILD_STRING(user
)),
360 JSON_BUILD_PAIR("group", JSON_BUILD_STRING(group
))));
362 return log_error_errno(r
, "Failed to build JSON object: %m");
364 json_variant_dump(v
, JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
, NULL
, NULL
);
368 case OUTPUT_FRIENDLY
:
369 /* Hmm, this is not particularly friendly, but not sure how we could do this better */
370 printf("%s: %s\n", group
, user
);
379 TABLE_STRING
, group
);
381 return table_log_add_error(r
);
386 assert_not_reached("Unexpected output mode");
392 static int display_memberships(int argc
, char *argv
[], void *userdata
) {
393 _cleanup_(table_unrefp
) Table
*table
= NULL
;
397 arg_output
= OUTPUT_TABLE
;
399 if (arg_output
== OUTPUT_TABLE
) {
400 table
= table_new("user", "group");
404 (void) table_set_sort(table
, (size_t) 0, (size_t) 1, (size_t) -1);
410 STRV_FOREACH(i
, argv
+ 1) {
411 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
413 if (streq(argv
[0], "users-in-group")) {
414 r
= membershipdb_by_group(*i
, arg_userdb_flags
, &iterator
);
416 return log_error_errno(r
, "Failed to enumerate users in group: %m");
417 } else if (streq(argv
[0], "groups-of-user")) {
418 r
= membershipdb_by_user(*i
, arg_userdb_flags
, &iterator
);
420 return log_error_errno(r
, "Failed to enumerate groups of user: %m");
422 assert_not_reached("Unexpected verb");
425 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
427 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
431 return log_error_errno(r
, "Selected membership database service is not available for this request.");
433 return log_error_errno(r
, "Failed acquire next membership: %m");
435 r
= show_membership(user
, group
, table
);
441 _cleanup_(userdb_iterator_freep
) UserDBIterator
*iterator
= NULL
;
443 r
= membershipdb_all(arg_userdb_flags
, &iterator
);
445 return log_error_errno(r
, "Failed to enumerate memberships: %m");
448 _cleanup_free_
char *user
= NULL
, *group
= NULL
;
450 r
= membershipdb_iterator_get(iterator
, &user
, &group
);
454 return log_error_errno(r
, "Selected membership database service is not available for this request.");
456 return log_error_errno(r
, "Failed acquire next membership: %m");
458 r
= show_membership(user
, group
, table
);
465 r
= table_print(table
, NULL
);
467 return table_log_print_error(r
);
473 static int display_services(int argc
, char *argv
[], void *userdata
) {
474 _cleanup_(table_unrefp
) Table
*t
= NULL
;
475 _cleanup_(closedirp
) DIR *d
= NULL
;
479 d
= opendir("/run/systemd/userdb/");
481 if (errno
== ENOENT
) {
482 log_info("No services.");
486 return log_error_errno(errno
, "Failed to open /run/systemd/userdb/: %m");
489 t
= table_new("service", "listening");
493 (void) table_set_sort(t
, (size_t) 0, (size_t) -1);
495 FOREACH_DIRENT(de
, d
, return -errno
) {
496 _cleanup_free_
char *j
= NULL
, *no
= NULL
;
497 union sockaddr_union sockaddr
;
498 socklen_t sockaddr_len
;
499 _cleanup_close_
int fd
= -1;
501 j
= path_join("/run/systemd/userdb/", de
->d_name
);
505 r
= sockaddr_un_set_path(&sockaddr
.un
, j
);
507 return log_error_errno(r
, "Path %s does not fit in AF_UNIX socket address: %m", j
);
510 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
512 return log_error_errno(r
, "Failed to allocate AF_UNIX/SOCK_STREAM socket: %m");
514 if (connect(fd
, &sockaddr
.un
, sockaddr_len
) < 0) {
515 no
= strjoin("No (", errno_to_name(errno
), ")");
520 r
= table_add_many(t
,
521 TABLE_STRING
, de
->d_name
,
522 TABLE_STRING
, no
?: "yes",
523 TABLE_SET_COLOR
, no
? ansi_highlight_red() : ansi_highlight_green());
525 return table_log_add_error(r
);
528 if (table_get_rows(t
) <= 0) {
529 log_info("No services.");
533 if (arg_output
== OUTPUT_JSON
)
534 table_print_json(t
, NULL
, JSON_FORMAT_PRETTY
|JSON_FORMAT_COLOR_AUTO
);
536 table_print(t
, NULL
);
541 static int ssh_authorized_keys(int argc
, char *argv
[], void *userdata
) {
542 _cleanup_(user_record_unrefp
) UserRecord
*ur
= NULL
;
545 r
= userdb_by_name(argv
[1], arg_userdb_flags
, &ur
);
547 return log_error_errno(r
, "User %s does not exist.", argv
[1]);
548 else if (r
== -EHOSTDOWN
)
549 return log_error_errno(r
, "Selected user database service is not available for this request.");
550 else if (r
== -EINVAL
)
551 return log_error_errno(r
, "Failed to find user %s: %m (Invalid user name?)", argv
[1]);
553 return log_error_errno(r
, "Failed to find user %s: %m", argv
[1]);
555 if (strv_isempty(ur
->ssh_authorized_keys
))
556 log_debug("User record for %s has no public SSH keys.", argv
[1]);
560 STRV_FOREACH(i
, ur
->ssh_authorized_keys
)
564 if (ur
->incomplete
) {
566 log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", ur
->user_name
);
572 static int help(int argc
, char *argv
[], void *userdata
) {
573 _cleanup_free_
char *link
= NULL
;
576 (void) pager_open(arg_pager_flags
);
578 r
= terminal_urlify_man("userdbctl", "1", &link
);
582 printf("%s [OPTIONS...] COMMAND ...\n\n"
583 "%sShow user and group information.%s\n"
585 " user [USER…] Inspect user\n"
586 " group [GROUP…] Inspect group\n"
587 " users-in-group [GROUP…] Show users that are members of specified group(s)\n"
588 " groups-of-user [USER…] Show groups the specified user(s) is a member of\n"
589 " services Show enabled database services\n"
591 " -h --help Show this help\n"
592 " --version Show package version\n"
593 " --no-pager Do not pipe output into a pager\n"
594 " --no-legend Do not show the headers and footers\n"
595 " --output=MODE Select output mode (classic, friendly, table, json)\n"
596 " -j Equivalent to --output=json\n"
597 " -s --service=SERVICE[:SERVICE…]\n"
598 " Query the specified service\n"
599 " --with-nss=BOOL Control whether to include glibc NSS data\n"
600 " -N Do not synthesize or include glibc NSS data\n"
601 " (Same as --synthesize=no --with-nss=no)\n"
602 " --synthesize=BOOL Synthesize root/nobody user\n"
603 "\nSee the %s for details.\n"
604 , program_invocation_short_name
605 , ansi_highlight(), ansi_normal()
612 static int parse_argv(int argc
, char *argv
[]) {
623 static const struct option options
[] = {
624 { "help", no_argument
, NULL
, 'h' },
625 { "version", no_argument
, NULL
, ARG_VERSION
},
626 { "no-pager", no_argument
, NULL
, ARG_NO_PAGER
},
627 { "no-legend", no_argument
, NULL
, ARG_NO_LEGEND
},
628 { "output", required_argument
, NULL
, ARG_OUTPUT
},
629 { "service", required_argument
, NULL
, 's' },
630 { "with-nss", required_argument
, NULL
, ARG_WITH_NSS
},
631 { "synthesize", required_argument
, NULL
, ARG_SYNTHESIZE
},
641 /* We are going to update this environment variable with our own, hence let's first read what is already set */
642 e
= getenv("SYSTEMD_ONLY_USERDB");
646 l
= strv_split(e
, ":");
650 strv_free(arg_services
);
657 c
= getopt_long(argc
, argv
, "hjs:N", options
, NULL
);
664 return help(0, NULL
, NULL
);
670 arg_pager_flags
|= PAGER_DISABLE
;
678 if (streq(optarg
, "classic"))
679 arg_output
= OUTPUT_CLASSIC
;
680 else if (streq(optarg
, "friendly"))
681 arg_output
= OUTPUT_FRIENDLY
;
682 else if (streq(optarg
, "json"))
683 arg_output
= OUTPUT_JSON
;
684 else if (streq(optarg
, "table"))
685 arg_output
= OUTPUT_TABLE
;
686 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 assert_se(unsetenv("SYSTEMD_ONLY_USERDB") == 0);
786 return dispatch_verb(argc
, argv
, verbs
, NULL
);
789 DEFINE_MAIN_FUNCTION(run
);