From ec75a254d29aa0beec6ab8c3cf7745bc38ea8ded Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 17 Sep 2024 12:06:49 +0200 Subject: [PATCH 1/4] Move show_menu() to terminal-util.h --- src/basic/terminal-util.c | 47 +++++++++++++++++++++++++++++++++++++++ src/basic/terminal-util.h | 1 + src/firstboot/firstboot.c | 46 -------------------------------------- 3 files changed, 48 insertions(+), 46 deletions(-) diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index cff1d50e42..d5593bd01d 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -29,6 +29,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "glyph-util.h" #include "hexdecoct.h" #include "inotify-util.h" #include "io-util.h" @@ -274,6 +275,52 @@ bool any_key_to_proceed(void) { return key != 'q'; } +int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) { + unsigned break_lines, break_modulo; + size_t n, per_column, i, j; + + assert(n_columns > 0); + + n = strv_length(x); + per_column = DIV_ROUND_UP(n, n_columns); + + break_lines = lines(); + if (break_lines > 2) + break_lines--; + + /* The first page gets two extra lines, since we want to show + * a title */ + break_modulo = break_lines; + if (break_modulo > 3) + break_modulo -= 3; + + for (i = 0; i < per_column; i++) { + + for (j = 0; j < n_columns; j++) { + _cleanup_free_ char *e = NULL; + + if (j * per_column + i >= n) + break; + + e = ellipsize(x[j * per_column + i], width, percentage); + if (!e) + return log_oom(); + + printf("%4zu) %-*s", j * per_column + i + 1, (int) width, e); + } + + putchar('\n'); + + /* on the first screen we reserve 2 extra lines for the title */ + if (i % break_lines == break_modulo) { + if (!any_key_to_proceed()) + return 0; + } + } + + return 0; +} + int open_terminal(const char *name, int mode) { _cleanup_close_ int fd = -EBADF; unsigned c = 0; diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index 180ca24ef4..b5c45c464b 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -79,6 +79,7 @@ int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl); int ask_char(char *ret, const char *replies, const char *text, ...) _printf_(3, 4); int ask_string(char **ret, const char *text, ...) _printf_(2, 3); bool any_key_to_proceed(void); +int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage); int vt_disallocate(const char *name); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 236c64634c..3bf11e7f21 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -132,52 +132,6 @@ static void print_welcome(int rfd) { done = true; } -static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned percentage) { - unsigned break_lines, break_modulo; - size_t n, per_column, i, j; - - assert(n_columns > 0); - - n = strv_length(x); - per_column = DIV_ROUND_UP(n, n_columns); - - break_lines = lines(); - if (break_lines > 2) - break_lines--; - - /* The first page gets two extra lines, since we want to show - * a title */ - break_modulo = break_lines; - if (break_modulo > 3) - break_modulo -= 3; - - for (i = 0; i < per_column; i++) { - - for (j = 0; j < n_columns; j++) { - _cleanup_free_ char *e = NULL; - - if (j * per_column + i >= n) - break; - - e = ellipsize(x[j * per_column + i], width, percentage); - if (!e) - return log_oom(); - - printf("%4zu) %-*s", j * per_column + i + 1, (int) width, e); - } - - putchar('\n'); - - /* on the first screen we reserve 2 extra lines for the title */ - if (i % break_lines == break_modulo) { - if (!any_key_to_proceed()) - return 0; - } - } - - return 0; -} - static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*is_valid)(const char *name), char **ret) { int r; From 164ca24d7464253e5f8375226b792ef8f6eaffd0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 16 Sep 2024 13:43:38 +0200 Subject: [PATCH 2/4] home: Prompt for auxiliary groups in homectl firstboot --- src/home/homectl.c | 132 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/src/home/homectl.c b/src/home/homectl.c index c193715d96..a2678c8140 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2424,6 +2424,58 @@ static int has_regular_user(void) { return false; } +static int acquire_group_list(char ***ret) { + _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; + _cleanup_strv_free_ char **groups = NULL; + int r; + + assert(ret); + + r = groupdb_all(USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, &iterator); + if (r == -ENOLINK) + log_debug_errno(r, "No groups found. (Didn't check via Varlink.)"); + else if (r == -ESRCH) + log_debug_errno(r, "No groups found."); + else if (r < 0) + return log_debug_errno(r, "Failed to enumerate groups, ignoring: %m"); + else { + for (;;) { + _cleanup_(group_record_unrefp) GroupRecord *gr = NULL; + + r = groupdb_iterator_get(iterator, &gr); + if (r == -ESRCH) + break; + if (r < 0) + return log_debug_errno(r, "Failed acquire next group: %m"); + + if (!IN_SET(group_record_disposition(gr), USER_REGULAR, USER_SYSTEM)) + continue; + + if (group_record_disposition(gr) == USER_REGULAR) { + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + + /* Filter groups here that belong to a specific user, and are named like them */ + + r = userdb_by_name(gr->group_name, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, &ur); + if (r < 0 && r != -ESRCH) + return log_debug_errno(r, "Failed to check if matching user exists for group '%s': %m", gr->group_name); + + if (r >= 0 && ur->gid == gr->gid && user_record_disposition(ur) == USER_REGULAR) + continue; + } + + r = strv_extend(&groups, gr->group_name); + if (r < 0) + return log_oom(); + } + } + + strv_sort(groups); + + *ret = TAKE_PTR(groups); + return !!*ret; +} + static int create_interactively(void) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *username = NULL; @@ -2476,6 +2528,86 @@ static int create_interactively(void) { if (r < 0) return log_error_errno(r, "Failed to set userName field: %m"); + _cleanup_strv_free_ char **available = NULL, **groups = NULL; + + for (;;) { + _cleanup_free_ char *s = NULL; + unsigned u; + + r = ask_string(&s, + "%s Please enter an auxiliary group for user %s (empty to continue, \"list\" to list available groups): ", + special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), username); + if (r < 0) + return log_error_errno(r, "Failed to query user for auxiliary group: %m"); + + if (isempty(s)) + break; + + if (streq(s, "list")) { + if (!available) { + r = acquire_group_list(&available); + if (r < 0) + log_warning_errno(r, "Failed to enumerate available groups, ignoring: %m"); + if (r == 0) + log_notice("Did not find any available groups"); + if (r <= 0) + continue; + } + + r = show_menu(available, /*n_columns=*/ 3, /*width=*/ 20, /*percentage=*/ 60); + if (r < 0) + return r; + + putchar('\n'); + continue; + }; + + if (available) { + r = safe_atou(s, &u); + if (r >= 0) { + if (u <= 0 || u > strv_length(available)) { + log_error("Specified entry number out of range."); + continue; + } + + log_info("Selected '%s'.", available[u-1]); + + r = strv_extend(&groups, available[u-1]); + if (r < 0) + return log_oom(); + + continue; + } + } + + if (!valid_user_group_name(s, /* flags= */ 0)) { + log_notice("Specified group name is not a valid UNIX group name, try again: %s", s); + continue; + } + + r = groupdb_by_name(s, USERDB_SUPPRESS_SHADOW|USERDB_EXCLUDE_DYNAMIC_USER, /*ret=*/ NULL); + if (r == -ESRCH) { + log_notice("Specified auxiliary group does not exist, try again: %s", s); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to check if specified group '%s' already exists: %m", s); + + log_info("Selected '%s'.", s); + + r = strv_extend(&groups, s); + if (r < 0) + return log_oom(); + } + + if (groups) { + strv_sort_uniq(groups); + + r = sd_json_variant_set_field_strv(&arg_identity_extra, "memberOf", groups); + if (r < 0) + return log_error_errno(r, "Failed to set memberOf field: %m"); + } + return create_home_common(/* input= */ NULL); } From 84edd52121ff9573d4da42ebbf85f73e07418a60 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Tue, 17 Sep 2024 23:01:13 +0200 Subject: [PATCH 3/4] home: Prompt for shell in homectl firstboot --- src/home/homectl.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/home/homectl.c b/src/home/homectl.c index a2678c8140..d1883f7421 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -2608,6 +2608,45 @@ static int create_interactively(void) { return log_error_errno(r, "Failed to set memberOf field: %m"); } + _cleanup_free_ char *shell = NULL; + + for (;;) { + shell = mfree(shell); + + r = ask_string(&shell, + "%s Please enter the shell to use for user %s (empty to skip): ", + special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET), username); + if (r < 0) + return log_error_errno(r, "Failed to query user for username: %m"); + + if (isempty(shell)) { + log_info("No data entered, skipping."); + break; + } + + if (!valid_shell(shell)) { + log_notice("Specified shell is not a valid UNIX shell path, try again: %s", shell); + continue; + } + + r = RET_NERRNO(access(shell, X_OK)); + if (r >= 0) + break; + + if (r != -ENOENT) + return log_error_errno(r, "Failed to check if shell %s exists: %m", shell); + + log_notice("Specified shell '%s' is not installed, try another one.", shell); + } + + if (shell) { + log_info("Selected %s as the shell for user %s", shell, username); + + r = sd_json_variant_set_field_string(&arg_identity_extra, "shell", shell); + if (r < 0) + return log_error_errno(r, "Failed to set shell field: %m"); + } + return create_home_common(/* input= */ NULL); } From 5f446751205e28b2cd68b9ec7e1125176c3783e0 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 18 Sep 2024 17:02:28 +0200 Subject: [PATCH 4/4] homectl: Acquire bus connection after querying for user input Otherwise when the user takes a long time to enter input the operation to create the user's home will fail with "transport endpoint not connected". --- src/home/homectl.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/home/homectl.c b/src/home/homectl.c index d1883f7421..d1cc8146b6 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -1411,12 +1411,6 @@ static int create_home_common(sd_json_variant *input) { _cleanup_hashmap_free_ Hashmap *blobs = NULL; int r; - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - r = acquire_new_home_record(input, &hr); if (r < 0) return r; @@ -1462,6 +1456,12 @@ static int create_home_common(sd_json_variant *input) { log_warning_errno(r, "Specified password does not pass quality checks (%s), proceeding anyway.", bus_error_message(&error, r)); } + r = acquire_bus(&bus); + if (r < 0) + return r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + for (;;) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; @@ -2477,7 +2477,6 @@ static int acquire_group_list(char ***ret) { } static int create_interactively(void) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *username = NULL; int r; @@ -2488,12 +2487,6 @@ static int create_interactively(void) { any_key_to_proceed(); - r = acquire_bus(&bus); - if (r < 0) - return r; - - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - (void) terminal_reset_defensive_locked(STDOUT_FILENO, /* switch_to_text= */ false); for (;;) {