diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index 59eb7acddd..551dcb8b27 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -5,16 +5,23 @@ #include #include +#include "sd-varlink.h" + #include "ask-password-api.h" #include "build.h" +#include "bus-polkit.h" #include "constants.h" +#include "json-util.h" #include "log.h" #include "macro.h" #include "main-func.h" #include "parse-argument.h" #include "pretty-print.h" +#include "string-table.h" #include "strv.h" #include "terminal-util.h" +#include "varlink-io.systemd.AskPassword.h" +#include "varlink-util.h" static const char *arg_icon = NULL; static const char *arg_id = NULL; /* identifier for 'ask-password' protocol */ @@ -26,6 +33,7 @@ static bool arg_multiple = false; static bool arg_no_output = false; static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE; static bool arg_newline = true; +static bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_message, freep); @@ -213,7 +221,7 @@ static int parse_argv(int argc, char *argv[]) { else { r = parse_boolean_argument("--emoji=", emoji, NULL); if (r < 0) - return r; + return r; SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r); } @@ -232,9 +240,164 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } + r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) + arg_varlink = true; + return 1; } +typedef enum EchoMode { + ECHO_OFF, + ECHO_ON, + ECHO_MASKED, + _ECHO_MODE_MAX, + _ECHO_MODE_INVALID = -EINVAL, +} EchoMode; + +static const char* echo_mode_table[_ECHO_MODE_MAX] = { + [ECHO_OFF] = "off", + [ECHO_ON] = "on", + [ECHO_MASKED] = "masked", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(echo_mode, EchoMode, ECHO_ON); + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_echo_mode, EchoMode, echo_mode_from_string); + +typedef struct MethodAskParameters { + const char *message; + const char *keyring; + const char *icon; + const char *id; + uint64_t timeout_usec; + uint64_t until_usec; + int accept_cached; + int push_cache; + EchoMode echo_mode; +} MethodAskParameters; + +static int vl_method_ask(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + + static const sd_json_dispatch_field dispatch_table[] = { + { "message", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodAskParameters, message), 0 }, + { "keyname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodAskParameters, keyring), 0 }, + { "icon", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodAskParameters, icon), 0 }, + { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodAskParameters, id), 0 }, + { "timeoutUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(MethodAskParameters, timeout_usec), 0 }, + { "untilUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(MethodAskParameters, until_usec), 0 }, + { "acceptCached", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodAskParameters, accept_cached), 0 }, + { "pushCache", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodAskParameters, push_cache) , 0 }, + { "echo", SD_JSON_VARIANT_STRING, dispatch_echo_mode, offsetof(MethodAskParameters, echo_mode), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Hashmap **polkit_registry = ASSERT_PTR(userdata); + MethodAskParameters p = { + .timeout_usec = DEFAULT_TIMEOUT_USEC, + .until_usec = UINT64_MAX, + .accept_cached = -1, + .push_cache = -1, + .echo_mode = _ECHO_MODE_INVALID, + }; + int r; + + assert(link); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = varlink_verify_polkit_async_full( + link, + /* bus= */ NULL, + "io.systemd.ask-password.ask", + /* details= */ NULL, + /* good_user= */ FLAGS_SET(arg_flags, ASK_PASSWORD_USER) ? getuid() : UID_INVALID, + /* flags= */ 0, + polkit_registry); + if (r <= 0) + return r; + + AskPasswordRequest req = { + .tty_fd = -EBADF, + .message = p.message ?: arg_message, + .icon = p.icon ?: arg_icon, + .id = p.id ?: arg_id, + .keyring = p.keyring ?: arg_key_name, + .credential = arg_credential_name, + .hup_fd = sd_varlink_get_input_fd(link), + }; + + /* Specifying the absolute or relative timeout as zero means: do not ask interactively, only check + * cache, hence leave the field at zero in that case. Otherwise we take the minimum of both times. */ + if (p.timeout_usec != 0 && p.until_usec != 0) + req.until = MIN(usec_add(now(CLOCK_MONOTONIC), p.timeout_usec), p.until_usec); + + /* If the timeout is set to zero, don't ask agents, just stick to cache */ + SET_FLAG(arg_flags, ASK_PASSWORD_NO_AGENT, req.until == 0); + + if (p.accept_cached >= 0) + SET_FLAG(arg_flags, ASK_PASSWORD_ACCEPT_CACHED, p.accept_cached); + + if (p.push_cache >= 0) + SET_FLAG(arg_flags, ASK_PASSWORD_PUSH_CACHE, p.push_cache); + + if (p.echo_mode >= 0) { + SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, p.echo_mode == ECHO_ON); + SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, p.echo_mode == ECHO_OFF); + } + + _cleanup_strv_free_erase_ char **l = NULL; + r = ask_password_auto(&req, arg_flags, &l); + if (r == -EUNATCH) + return sd_varlink_error(link, "io.systemd.AskPassword.NoPasswordAvailable", NULL); + if (r == -ETIME) + return sd_varlink_error(link, "io.systemd.AskPassword.TimeoutReached", NULL); + if (r == -ECONNRESET) { /* POLLHUP on the varlink fd we passed in via .hup_fd */ + sd_varlink_close(link); + return 1; + } + if (r < 0) + return r; + + _cleanup_(sd_json_variant_unrefp) sd_json_variant *vl = NULL; + r = sd_json_variant_new_array_strv(&vl, l); + if (r < 0) + return r; + + sd_json_variant_sensitive(vl); + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR("passwords", SD_JSON_BUILD_VARIANT(vl))); +} + +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + int r; + + r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_INHERIT_USERDATA, /* userdata= */ &polkit_registry); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_AskPassword); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method(varlink_server, "io.systemd.AskPassword.Ask", vl_method_ask); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink method: %m"); + + r = sd_varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + static int run(int argc, char *argv[]) { _cleanup_strv_free_erase_ char **l = NULL; usec_t timeout; @@ -249,17 +412,23 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_varlink) + return vl_server(); /* Invocation as Varlink service */ + timeout = arg_timeout > 0 ? usec_add(now(CLOCK_MONOTONIC), arg_timeout) : 0; AskPasswordRequest req = { + .tty_fd = -EBADF, .message = arg_message, .icon = arg_icon, .id = arg_id, .keyring = arg_key_name, .credential = arg_credential_name ?: "password", + .until = timeout, + .hup_fd = -EBADF, }; - r = ask_password_auto(&req, timeout, arg_flags, &l); + r = ask_password_auto(&req, arg_flags, &l); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); diff --git a/src/ask-password/io.systemd.ask-password.policy b/src/ask-password/io.systemd.ask-password.policy new file mode 100644 index 0000000000..8fddf5c07e --- /dev/null +++ b/src/ask-password/io.systemd.ask-password.policy @@ -0,0 +1,31 @@ + + + + + + + + The systemd Project + https://systemd.io + + + Allow to query the user interactively for a password + Authentication is required for an application to ask the user interactively for a password. + + auth_admin_keep + auth_admin_keep + yes + + + + diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 7ad264d882..884521bcfa 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -984,9 +984,12 @@ int verb_install(int argc, char *argv[], void *userdata) { arg_private_key_source, arg_private_key, &(AskPasswordRequest) { + .tty_fd = -EBADF, .id = "bootctl-private-key-pin", .keyring = arg_private_key, .credential = "bootctl.private-key-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }, &private_key, &ui); diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c index f6b53d4a76..c6888cc83b 100644 --- a/src/cryptenroll/cryptenroll-password.c +++ b/src/cryptenroll/cryptenroll-password.c @@ -55,11 +55,14 @@ int load_volume_key_password( return log_oom(); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = question, .icon = "drive-harddisk", .id = id, .keyring = "cryptenroll", .credential = "cryptenroll.passphrase", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; for (;;) { @@ -69,7 +72,7 @@ int load_volume_key_password( return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up."); - r = ask_password_auto(&req, USEC_INFINITY, ask_password_flags, &passwords); + r = ask_password_auto(&req, ask_password_flags, &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -130,10 +133,13 @@ int enroll_password( return log_oom(); AskPasswordRequest req = { + .tty_fd = -EBADF, .icon = "drive-harddisk", .id = id, .keyring = "cryptenroll", .credential = "cryptenroll.new-passphrase", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; for (;;) { @@ -150,7 +156,7 @@ int enroll_password( req.message = question; - r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &passwords); + r = ask_password_auto(&req, /* flags= */ 0, &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -163,7 +169,7 @@ int enroll_password( req.message = question; - r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &passwords2); + r = ask_password_auto(&req, /* flags= */ 0, &passwords2); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index ca163ef3c2..fd03fecc62 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -119,16 +119,18 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) { SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up."); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = "Please enter TPM2 PIN:", .icon = "drive-harddisk", .keyring = "tpm2-pin", .credential = "cryptenroll.new-tpm2-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; pin = strv_free_erase(pin); r = ask_password_auto( &req, - /* until= */ USEC_INFINITY, /* flags= */ 0, &pin); if (r < 0) @@ -139,7 +141,6 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) { r = ask_password_auto( &req, - USEC_INFINITY, /* flags= */ 0, &pin2); if (r < 0) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 1da9e21d8e..e5f21bbce7 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -906,17 +906,20 @@ static int get_password( return log_oom(); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = text, .icon = "drive-harddisk", .id = id, .keyring = "cryptsetup", .credential = "cryptsetup.passphrase", + .until = until, + .hup_fd = -EBADF, }; if (ignore_cached) flags &= ~ASK_PASSWORD_ACCEPT_CACHED; - r = ask_password_auto(&req, until, flags, &passwords); + r = ask_password_auto(&req, flags, &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -937,7 +940,7 @@ static int get_password( req.message = text; req.id = id; - r = ask_password_auto(&req, until, flags, &passwords2); + r = ask_password_auto(&req, flags, &passwords2); if (r < 0) return log_error_errno(r, "Failed to query verification password: %m"); @@ -1422,13 +1425,16 @@ static int crypt_activate_by_token_pin_ask_password( pins = strv_free_erase(pins); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = message, .icon = "drive-harddisk", .keyring = keyring, .credential = credential, + .until = until, + .hup_fd = -EBADF, }; - r = ask_password_auto(&req, until, flags, &pins); + r = ask_password_auto(&req, flags, &pins); if (r < 0) return r; diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 9be62b8df3..15abb53be8 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -735,10 +735,13 @@ static int prompt_root_password(int rfd) { _cleanup_free_ char *error = NULL; AskPasswordRequest req = { + .tty_fd = -EBADF, .message = msg1, + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; - r = ask_password_tty(-EBADF, &req, /* until= */ 0, /* flags= */ 0, /* flag_file= */ NULL, &a); + r = ask_password_tty(&req, /* flags= */ 0, &a); if (r < 0) return log_error_errno(r, "Failed to query root password: %m"); if (strv_length(a) != 1) @@ -760,7 +763,7 @@ static int prompt_root_password(int rfd) { req.message = msg2; - r = ask_password_tty(-EBADF, &req, /* until= */ 0, /* flags= */ 0, /* flag_file= */ NULL, &b); + r = ask_password_tty(&req, /* flags= */ 0, &b); if (r < 0) return log_error_errno(r, "Failed to query root password: %m"); if (strv_length(b) != 1) diff --git a/src/home/homectl.c b/src/home/homectl.c index b3aacbcbcf..104c35454b 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -263,13 +263,16 @@ static int acquire_existing_password( return log_oom(); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = question, .icon = "user-home", .keyring = "home-password", .credential = "home.password", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; - r = ask_password_auto(&req, USEC_INFINITY, flags, &password); + r = ask_password_auto(&req, flags, &password); if (r == -EUNATCH) { /* EUNATCH is returned if no password was found and asking interactively was * disabled via the flags. Not an error for us. */ log_debug_errno(r, "No passwords acquired."); @@ -321,13 +324,16 @@ static int acquire_recovery_key( return log_oom(); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = question, .icon = "user-home", .keyring = "home-recovery-key", .credential = "home.recovery-key", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; - r = ask_password_auto(&req, USEC_INFINITY, flags, &recovery_key); + r = ask_password_auto(&req, flags, &recovery_key); if (r == -EUNATCH) { /* EUNATCH is returned if no recovery key was found and asking interactively was * disabled via the flags. Not an error for us. */ log_debug_errno(r, "No recovery keys acquired."); @@ -375,13 +381,16 @@ static int acquire_token_pin( return log_oom(); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = question, .icon = "user-home", .keyring = "token-pin", .credential = "home.token-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; - r = ask_password_auto(&req, USEC_INFINITY, flags, &pin); + r = ask_password_auto(&req, flags, &pin); if (r == -EUNATCH) { /* EUNATCH is returned if no PIN was found and asking interactively was disabled * via the flags. Not an error for us. */ log_debug_errno(r, "No security token PINs acquired."); @@ -1229,15 +1238,17 @@ static int acquire_new_password( return log_oom(); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = question, .icon = "user-home", .keyring = "home-password", .credential = "home.new-password", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; r = ask_password_auto( &req, - USEC_INFINITY, /* flags= */ 0, /* no caching, we want to collect a new password here after all */ &first); if (r < 0) @@ -1253,7 +1264,6 @@ static int acquire_new_password( r = ask_password_auto( &req, - USEC_INFINITY, /* flags= */ 0, /* no caching */ &second); if (r < 0) diff --git a/src/keyutil/keyutil.c b/src/keyutil/keyutil.c index 70176c76c7..23459e48f5 100644 --- a/src/keyutil/keyutil.c +++ b/src/keyutil/keyutil.c @@ -182,9 +182,12 @@ static int verb_validate(int argc, char *argv[], void *userdata) { arg_private_key_source, arg_private_key, &(AskPasswordRequest) { + .tty_fd = -EBADF, .id = "keyutil-private-key-pin", .keyring = arg_private_key, .credential = "keyutil.private-key-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }, &private_key, &ui); @@ -238,9 +241,12 @@ static int verb_public(int argc, char *argv[], void *userdata) { arg_private_key_source, arg_private_key, &(AskPasswordRequest) { + .tty_fd = -EBADF, .id = "keyutil-private-key-pin", .keyring = arg_private_key, .credential = "keyutil.private-key-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }, &private_key, &ui); diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index d2747537e6..ccb9398bcd 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -1062,9 +1062,12 @@ global: LIBSYSTEMD_258 { global: + sd_device_enumerator_add_all_parents; + sd_device_enumerator_add_all_parents; sd_json_variant_type_from_string; sd_json_variant_type_to_string; + sd_varlink_get_input_fd; + sd_varlink_get_output_fd; sd_varlink_reset_fds; sd_varlink_server_listen_name; - sd_device_enumerator_add_all_parents; } LIBSYSTEMD_257; diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index aa5aaf8df8..59228cc08b 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -1673,6 +1673,30 @@ _public_ int sd_varlink_get_fd(sd_varlink *v) { return v->input_fd; } +_public_ int sd_varlink_get_input_fd(sd_varlink *v) { + + assert_return(v, -EINVAL); + + if (v->state == VARLINK_DISCONNECTED) + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); + if (v->input_fd < 0) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "No valid input fd."); + + return v->input_fd; +} + +_public_ int sd_varlink_get_output_fd(sd_varlink *v) { + + assert_return(v, -EINVAL); + + if (v->state == VARLINK_DISCONNECTED) + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); + if (v->output_fd < 0) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBADF), "No valid output fd."); + + return v->output_fd; +} + _public_ int sd_varlink_get_events(sd_varlink *v) { int ret = 0; diff --git a/src/measure/measure.c b/src/measure/measure.c index 22dd183125..18461797b7 100644 --- a/src/measure/measure.c +++ b/src/measure/measure.c @@ -888,9 +888,12 @@ static int verb_sign(int argc, char *argv[], void *userdata) { arg_private_key_source, arg_private_key, &(AskPasswordRequest) { + .tty_fd = -EBADF, .id = "measure-private-key-pin", .keyring = arg_private_key, .credential = "measure.private-key-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }, &privkey, &ui); diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index ef4d1a1f17..54c31c3a82 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -297,6 +297,29 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va return sd_varlink_reply(link, NULL); } +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + int r; + + r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ROOT_ONLY, /* userdata= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_PCRExtend); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method(varlink_server, "io.systemd.PCRExtend.Extend", vl_method_extend); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink method: %m"); + + r = sd_varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + static int run(int argc, char *argv[]) { _cleanup_free_ char *word = NULL; Tpm2UserspaceEventType event; @@ -308,29 +331,8 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - if (arg_varlink) { - _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; - - /* Invocation as Varlink service */ - - r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ROOT_ONLY, NULL); - if (r < 0) - return log_error_errno(r, "Failed to allocate Varlink server: %m"); - - r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_PCRExtend); - if (r < 0) - return log_error_errno(r, "Failed to add Varlink interface: %m"); - - r = sd_varlink_server_bind_method(varlink_server, "io.systemd.PCRExtend.Extend", vl_method_extend); - if (r < 0) - return log_error_errno(r, "Failed to bind Varlink method: %m"); - - r = sd_varlink_server_loop_auto(varlink_server); - if (r < 0) - return log_error_errno(r, "Failed to run Varlink event loop: %m"); - - return EXIT_SUCCESS; - } + if (arg_varlink) + return vl_server(); /* Invocation as Varlink service */ if (arg_file_system) { if (optind != argc) diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index 9562bf2a89..72b13f8fac 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -4551,14 +4551,16 @@ static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { _cleanup_(strv_free_erasep) char **l = NULL; AskPasswordRequest req = { + .tty_fd = -EBADF, .message = "Recovery PIN", .id = "pcrlock-recovery-pin", .credential = "pcrlock.recovery-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; r = ask_password_auto( &req, - /* until= */ 0, /* flags= */ 0, &l); if (r < 0) diff --git a/src/repart/repart.c b/src/repart/repart.c index 1a88185c47..7934a97435 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -8572,9 +8572,12 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * arg_private_key_source, arg_private_key, &(AskPasswordRequest) { + .tty_fd = -EBADF, .id = "repart-private-key-pin", .keyring = arg_private_key, .credential = "repart.private-key-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }, &private_key, &ui); diff --git a/src/sbsign/sbsign.c b/src/sbsign/sbsign.c index d17fdfea5e..92b8284219 100644 --- a/src/sbsign/sbsign.c +++ b/src/sbsign/sbsign.c @@ -203,9 +203,12 @@ static int verb_sign(int argc, char *argv[], void *userdata) { arg_private_key_source, arg_private_key, &(AskPasswordRequest) { + .tty_fd = -EBADF, .id = "sbsign-private-key-pin", .keyring = arg_private_key, .credential = "sbsign.private-key-pin", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }, &private_key, &ui); diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 2e49096f82..df8915082b 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -309,9 +309,7 @@ static int backspace_string(int ttyfd, const char *str) { int ask_password_plymouth( const AskPasswordRequest *req, - usec_t until, AskPasswordFlags flags, - const char *flag_file, char ***ret) { _cleanup_close_ int fd = -EBADF, inotify_fd = -EBADF; @@ -328,12 +326,12 @@ int ask_password_plymouth( const char *message = req && req->message ? req->message : "Password:"; - if (flag_file) { + if (req->flag_file) { inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); if (inotify_fd < 0) return -errno; - if (inotify_add_watch(inotify_fd, flag_file, IN_ATTRIB) < 0) /* for the link count */ + if (inotify_add_watch(inotify_fd, req->flag_file, IN_ATTRIB) < 0) /* for the link count */ return -errno; } @@ -357,25 +355,40 @@ int ask_password_plymouth( enum { POLL_SOCKET, - POLL_INOTIFY, /* Must be last, because optional */ + POLL_TWO, + POLL_THREE, _POLL_MAX, }; struct pollfd pollfd[_POLL_MAX] = { - [POLL_SOCKET] = { .fd = fd, .events = POLLIN }, - [POLL_INOTIFY] = { .fd = inotify_fd, .events = POLLIN }, + [POLL_SOCKET] = { + .fd = fd, + .events = POLLIN, + }, }; - size_t n_pollfd = inotify_fd >= 0 ? _POLL_MAX : _POLL_MAX-1; + size_t n_pollfd = POLL_SOCKET + 1, inotify_idx = SIZE_MAX, hup_fd_idx = SIZE_MAX; + if (inotify_fd >= 0) + pollfd[inotify_idx = n_pollfd++] = (struct pollfd) { + .fd = inotify_fd, + .events = POLLIN, + }; + if (req->hup_fd >= 0) + pollfd[hup_fd_idx = n_pollfd++] = (struct pollfd) { + .fd = req->hup_fd, + .events = POLLHUP, + }; + + assert(n_pollfd <= _POLL_MAX); for (;;) { usec_t timeout; - if (until > 0) - timeout = usec_sub_unsigned(until, now(CLOCK_MONOTONIC)); + if (req->until > 0) + timeout = usec_sub_unsigned(req->until, now(CLOCK_MONOTONIC)); else timeout = USEC_INFINITY; - if (flag_file && access(flag_file, F_OK) < 0) + if (req->flag_file && access(req->flag_file, F_OK) < 0) return -errno; r = ppoll_usec(pollfd, n_pollfd, timeout); @@ -386,7 +399,10 @@ int ask_password_plymouth( if (r == 0) return -ETIME; - if (inotify_fd >= 0 && pollfd[POLL_INOTIFY].revents != 0) + if (req->hup_fd >= 0 && pollfd[hup_fd_idx].revents & POLLHUP) + return -ECONNRESET; + + if (inotify_fd >= 0 && pollfd[inotify_idx].revents != 0) (void) flush_fd(inotify_fd); if (pollfd[POLL_SOCKET].revents == 0) @@ -464,11 +480,8 @@ int ask_password_plymouth( #define SKIPPED "(skipped)" int ask_password_tty( - int ttyfd, const AskPasswordRequest *req, - usec_t until, AskPasswordFlags flags, - const char *flag_file, char ***ret) { bool reset_tty = false, dirty = false, use_color = false, press_tab_visible = false; @@ -493,15 +506,14 @@ int ask_password_tty( if (!FLAGS_SET(flags, ASK_PASSWORD_HIDE_EMOJI) && emoji_enabled()) message = strjoina(special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY), " ", message); - if (flag_file || (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyring)) { + if (req->flag_file || (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyring)) { inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); if (inotify_fd < 0) return -errno; } - if (flag_file) { - if (inotify_add_watch(inotify_fd, flag_file, IN_ATTRIB /* for the link count */) < 0) + if (req->flag_file) + if (inotify_add_watch(inotify_fd, req->flag_file, IN_ATTRIB /* for the link count */) < 0) return -errno; - } if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && req && keyring) { r = ask_password_keyring(req, flags, ret); if (r >= 0) @@ -529,8 +541,11 @@ int ask_password_tty( CLEANUP_ERASE(passphrase); /* If the caller didn't specify a TTY, then use the controlling tty, if we can. */ - if (ttyfd < 0) + int ttyfd; + if (req->tty_fd < 0) ttyfd = cttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC); + else + ttyfd = req->tty_fd; if (ttyfd >= 0) { if (tcgetattr(ttyfd, &old_termios) < 0) @@ -570,28 +585,44 @@ int ask_password_tty( enum { POLL_TTY, - POLL_INOTIFY, /* Must be last, because optional */ + POLL_TWO, + POLL_THREE, _POLL_MAX, }; struct pollfd pollfd[_POLL_MAX] = { - [POLL_TTY] = { .fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO, .events = POLLIN }, - [POLL_INOTIFY] = { .fd = inotify_fd, .events = POLLIN }, + [POLL_TTY] = { + .fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO, + .events = POLLIN, + }, }; - size_t n_pollfd = inotify_fd >= 0 ? _POLL_MAX : _POLL_MAX-1; + size_t n_pollfd = POLL_TTY + 1, inotify_idx = SIZE_MAX, hup_fd_idx = SIZE_MAX; + + if (inotify_fd >= 0) + pollfd[inotify_idx = n_pollfd++] = (struct pollfd) { + .fd = inotify_fd, + .events = POLLIN, + }; + if (req->hup_fd >= 0) + pollfd[hup_fd_idx = n_pollfd++] = (struct pollfd) { + .fd = req->hup_fd, + .events = POLLHUP, + }; + + assert(n_pollfd <= _POLL_MAX); for (;;) { _cleanup_(erase_char) char c; usec_t timeout; ssize_t n; - if (until > 0) - timeout = usec_sub_unsigned(until, now(CLOCK_MONOTONIC)); + if (req->until > 0) + timeout = usec_sub_unsigned(req->until, now(CLOCK_MONOTONIC)); else timeout = USEC_INFINITY; - if (flag_file) { - r = RET_NERRNO(access(flag_file, F_OK)); + if (req->flag_file) { + r = RET_NERRNO(access(req->flag_file, F_OK)); if (r < 0) goto finish; } @@ -606,7 +637,12 @@ int ask_password_tty( goto finish; } - if (inotify_fd >= 0 && pollfd[POLL_INOTIFY].revents != 0 && keyring) { + if (req->hup_fd >= 0 && pollfd[hup_fd_idx].revents & POLLHUP) { + r = -ECONNRESET; + goto finish; + } + + if (inotify_fd >= 0 && pollfd[inotify_idx].revents != 0 && keyring) { (void) flush_fd(inotify_fd); r = ask_password_keyring(req, flags, ret); @@ -800,7 +836,6 @@ static int create_socket(const char *askpwdir, char **ret) { int ask_password_agent( const AskPasswordRequest *req, - usec_t until, AskPasswordFlags flags, char ***ret) { @@ -820,6 +855,10 @@ int ask_password_agent( if (FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) return -EUNATCH; + /* We don't support the flag file concept for now when querying via the agent logic */ + if (req->flag_file) + return -EOPNOTSUPP; + assert_se(sigemptyset(&mask) >= 0); assert_se(sigset_add_many(&mask, SIGINT, SIGTERM) >= 0); assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0); @@ -891,7 +930,7 @@ int ask_password_agent( socket_name, FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED), FLAGS_SET(flags, ASK_PASSWORD_ECHO), - until, + req->until, FLAGS_SET(flags, ASK_PASSWORD_SILENT)); if (req) { @@ -924,16 +963,29 @@ int ask_password_agent( enum { POLL_SOCKET, POLL_SIGNAL, - POLL_INOTIFY, /* Must be last, because optional */ + POLL_THREE, + POLL_FOUR, _POLL_MAX }; struct pollfd pollfd[_POLL_MAX] = { [POLL_SOCKET] = { .fd = socket_fd, .events = POLLIN }, [POLL_SIGNAL] = { .fd = signal_fd, .events = POLLIN }, - [POLL_INOTIFY] = { .fd = inotify_fd, .events = POLLIN }, }; - size_t n_pollfd = inotify_fd >= 0 ? _POLL_MAX : _POLL_MAX - 1; + size_t n_pollfd = POLL_SIGNAL + 1, inotify_idx = SIZE_MAX, hup_fd_idx = SIZE_MAX; + + if (inotify_fd >= 0) + pollfd[inotify_idx = n_pollfd++] = (struct pollfd) { + .fd = inotify_fd, + .events = POLLIN, + }; + if (req->hup_fd >= 0) + pollfd[hup_fd_idx = n_pollfd ++] = (struct pollfd) { + .fd = req->hup_fd, + .events = POLLHUP, + }; + + assert(n_pollfd <= _POLL_MAX); for (;;) { CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control; @@ -943,8 +995,8 @@ int ask_password_agent( usec_t timeout; ssize_t n; - if (until > 0) - timeout = usec_sub_unsigned(until, now(CLOCK_MONOTONIC)); + if (req->until > 0) + timeout = usec_sub_unsigned(req->until, now(CLOCK_MONOTONIC)); else timeout = USEC_INFINITY; @@ -963,7 +1015,10 @@ int ask_password_agent( goto finish; } - if (inotify_fd >= 0 && pollfd[POLL_INOTIFY].revents != 0) { + if (req->hup_fd >= 0 && pollfd[hup_fd_idx].revents & POLLHUP) + return -ECONNRESET; + + if (inotify_fd >= 0 && pollfd[inotify_idx].revents != 0) { (void) flush_fd(inotify_fd); if (req && req->keyring) { @@ -1103,7 +1158,6 @@ static int ask_password_credential(const AskPasswordRequest *req, AskPasswordFla int ask_password_auto( const AskPasswordRequest *req, - usec_t until, AskPasswordFlags flags, char ***ret) { @@ -1111,6 +1165,17 @@ int ask_password_auto( assert(ret); + /* Returns the following well-known errors: + * + * -ETIME → a timeout was specified and hit + * -EUNATCH → couldn't ask interactively and no cached password available either + * -ENOENT → the specified flag file disappeared + * -ECANCELED → the user explicitly cancelled the request + * -EINTR → SIGINT/SIGTERM where received during the query + * -ENOEXEC → headless mode was requested but no password could be acquired non-interactively + * -ECONNRESET → a POLLHUP has been seen on the specified hup_fd + */ + if (!FLAGS_SET(flags, ASK_PASSWORD_NO_CREDENTIAL) && req && req->credential) { r = ask_password_credential(req, flags, ret); if (r != -ENOKEY) @@ -1127,10 +1192,10 @@ int ask_password_auto( } if (!FLAGS_SET(flags, ASK_PASSWORD_NO_TTY) && isatty_safe(STDIN_FILENO)) - return ask_password_tty(-EBADF, req, until, flags, NULL, ret); + return ask_password_tty(req, flags, ret); if (!FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) - return ask_password_agent(req, until, flags, ret); + return ask_password_agent(req, flags, ret); return -EUNATCH; } diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h index b3cb407e51..03c6d1abdb 100644 --- a/src/shared/ask-password-api.h +++ b/src/shared/ask-password-api.h @@ -26,11 +26,15 @@ typedef struct AskPasswordRequest { const char *icon; /* freedesktop icon spec name */ const char *id; /* some identifier used for this prompt for the "ask-password" protocol */ const char *credential; /* $CREDENTIALS_DIRECTORY credential name */ + const char *flag_file; /* Once this flag file disappears abort the query */ + int tty_fd; /* If querying on a TTY, the TTY to query on (or -EBADF) */ + int hup_fd; /* An extra fd to watch for POLLHUP, in which case to abort the query */ + usec_t until; /* CLOCK_MONOTONIC time until which to show the prompt (if zero: forever) */ } AskPasswordRequest; -int ask_password_tty(int tty_fd, const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); -int ask_password_plymouth(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); -int ask_password_agent(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flag, char ***ret); -int ask_password_auto(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flag, char ***ret); +int ask_password_tty(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret); +int ask_password_plymouth(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret); +int ask_password_agent(const AskPasswordRequest *req, AskPasswordFlags flag, char ***ret); +int ask_password_auto(const AskPasswordRequest *req, AskPasswordFlags flag, char ***ret); int acquire_user_ask_password_directory(char **ret); diff --git a/src/shared/cryptsetup-fido2.c b/src/shared/cryptsetup-fido2.c index 1e1ef6dec0..8d4c179183 100644 --- a/src/shared/cryptsetup-fido2.c +++ b/src/shared/cryptsetup-fido2.c @@ -111,15 +111,18 @@ int acquire_fido2_key( if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable."); - static const AskPasswordRequest req = { + AskPasswordRequest req = { + .tty_fd = -EBADF, .message = "Please enter security token PIN:", .icon = "drive-harddisk", .keyring = "fido2-pin", .credential = "cryptsetup.fido2-pin", + .until = until, + .hup_fd = -EBADF, }; pins = strv_free_erase(pins); - r = ask_password_auto(&req, until, askpw_flags, &pins); + r = ask_password_auto(&req, askpw_flags, &pins); if (r < 0) return log_error_errno(r, "Failed to ask for user password: %m"); diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c index bc4fb100bb..c1cd67bacd 100644 --- a/src/shared/cryptsetup-tpm2.c +++ b/src/shared/cryptsetup-tpm2.c @@ -35,14 +35,17 @@ static int get_pin( "Use the '$PIN' environment variable."); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = "Please enter TPM2 PIN:", .icon = "drive-harddisk", .keyring = "tpm2-pin", .credential = askpw_credential, + .until = until, + .hup_fd = -EBADF, }; pin = strv_free_erase(pin); - r = ask_password_auto(&req, until, askpw_flags, &pin); + r = ask_password_auto(&req, askpw_flags, &pin); if (r < 0) return log_error_errno(r, "Failed to ask for user pin: %m"); assert(strv_length(pin) == 1); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 39c5c8a21a..86a549477c 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -3077,13 +3077,16 @@ int dissected_image_decrypt_interactively( z = strv_free_erase(z); static const AskPasswordRequest req = { + .tty_fd = -EBADF, .message = "Please enter image passphrase:", .id = "dissect", .keyring = "dissect", .credential = "dissect.passphrase", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; - r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &z); + r = ask_password_auto(&req, /* flags= */ 0, &z); if (r < 0) return log_error_errno(r, "Failed to query for passphrase: %m"); diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index b518dec7fa..101641e7fe 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -857,13 +857,16 @@ int fido2_generate_hmac_hash( for (;;) { _cleanup_strv_free_erase_ char **pin = NULL; AskPasswordRequest req = { + .tty_fd = -EBADF, .message = "Please enter security token PIN:", .icon = askpw_icon, .keyring = "fido2-pin", .credential = askpw_credential, + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; - r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &pin); + r = ask_password_auto(&req, /* flags= */ 0, &pin); if (r < 0) return log_error_errno(r, "Failed to acquire user PIN: %m"); diff --git a/src/shared/meson.build b/src/shared/meson.build index af9ef74b32..213d12d7a8 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -177,6 +177,7 @@ shared_sources = files( 'userdb-dropin.c', 'userdb.c', 'varlink-idl-common.c', + 'varlink-io.systemd.AskPassword.c', 'varlink-io.systemd.BootControl.c', 'varlink-io.systemd.Credentials.c', 'varlink-io.systemd.Hostname.c', diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index 417f842546..b60fc67b26 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -1399,7 +1399,7 @@ static int openssl_ask_password_ui_read(UI *ui, UI_STRING *uis) { req->message = UI_get0_output_string(uis); _cleanup_(strv_freep) char **l = NULL; - r = ask_password_auto(req, /*until=*/ 0, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &l); + r = ask_password_auto(req, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &l); if (r < 0) { log_error_errno(r, "Failed to query for PIN: %m"); return 0; diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index c7fa3eccb2..a38a91279d 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -380,15 +380,18 @@ int pkcs11_token_login( return log_oom(); AskPasswordRequest req = { + .tty_fd = -EBADF, .message = text, .icon = askpw_icon, .id = id, .keyring = askpw_keyring, .credential = askpw_credential, + .until = until, + .hup_fd = -EBADF, }; /* We never cache PINs, simply because it's fatal if we use wrong PINs, since usually there are only 3 tries */ - r = ask_password_auto(&req, until, askpw_flags, &passwords); + r = ask_password_auto(&req, askpw_flags, &passwords); if (r < 0) return log_error_errno(r, "Failed to query PIN for security token '%s': %m", token_label); } diff --git a/src/shared/varlink-io.systemd.AskPassword.c b/src/shared/varlink-io.systemd.AskPassword.c new file mode 100644 index 0000000000..86e0a803c8 --- /dev/null +++ b/src/shared/varlink-io.systemd.AskPassword.c @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.AskPassword.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + EchoMode, + SD_VARLINK_FIELD_COMMENT("Request that the password is prompted for without any visual feedback"), + SD_VARLINK_DEFINE_ENUM_VALUE(off), + SD_VARLINK_FIELD_COMMENT("Show the password in plaintext as it is typed in"), + SD_VARLINK_DEFINE_ENUM_VALUE(on), + SD_VARLINK_FIELD_COMMENT("Provide visual feedback as the password is typed, but mask the password plaintext"), + SD_VARLINK_DEFINE_ENUM_VALUE(masked)); + +static SD_VARLINK_DEFINE_METHOD( + Ask, + SD_VARLINK_FIELD_COMMENT("The message to show when prompting for the password"), + SD_VARLINK_DEFINE_INPUT(message, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The name for the kernel keyring entry used for caching"), + SD_VARLINK_DEFINE_INPUT(keyname, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("The icon name to display, following the freedesktop.org icon naming specification"), + SD_VARLINK_DEFINE_INPUT(icon, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("An recognizable id for the password prompt"), + SD_VARLINK_DEFINE_INPUT(id, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Timeout in µs (relative, CLOCK_MONOTONIC; set to zero to only check cache and not query interactively; set to UINT64_MAX to disable relative timeout; if not set defaults to 90s)"), + SD_VARLINK_DEFINE_INPUT(timeoutUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Timeout in µs (absolute, CLOCK_MONOTONIC; if both timeoutUSec and untilUSec are specified the earlier of the two is used; " + "set to zero to only check cache and not query interactively; leave unset or set to UINT64_NAX to disable absolute timeout)"), + SD_VARLINK_DEFINE_INPUT(untilUSec, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether to accept cached passwords from the kernel keyring"), + SD_VARLINK_DEFINE_INPUT(acceptCached, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether to push acquired passwords into the kernel keyring"), + SD_VARLINK_DEFINE_INPUT(pushCache, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Whether to give visual feedback when typing in the password"), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(echo, EchoMode, SD_VARLINK_NULLABLE), + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("List of acquired passwords. This typically contains one entry, but might contain more in case multiple passwords were previously cached."), + SD_VARLINK_DEFINE_OUTPUT(passwords, SD_VARLINK_STRING, SD_VARLINK_ARRAY)); + +static SD_VARLINK_DEFINE_ERROR(NoPasswordAvailable); +static SD_VARLINK_DEFINE_ERROR(TimeoutReached); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_AskPassword, + "io.systemd.AskPassword", + SD_VARLINK_INTERFACE_COMMENT("An interface for interactively asking the user for a password"), + SD_VARLINK_SYMBOL_COMMENT("Encodes whether to provide visual feedback as the password is typed in"), + &vl_type_EchoMode, + SD_VARLINK_SYMBOL_COMMENT("Interactively ask the user for a password, or answer from a previously cached entry"), + &vl_method_Ask, + SD_VARLINK_SYMBOL_COMMENT("No password available, because none was provided in the cache, and no agent was asked"), + &vl_error_NoPasswordAvailable, + SD_VARLINK_SYMBOL_COMMENT("Query timeout reached, user did not provide a password in time"), + &vl_error_TimeoutReached); diff --git a/src/shared/varlink-io.systemd.AskPassword.h b/src/shared/varlink-io.systemd.AskPassword.h new file mode 100644 index 0000000000..1be83f4c90 --- /dev/null +++ b/src/shared/varlink-io.systemd.AskPassword.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_AskPassword; diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h index 083be6598d..223d02f4d7 100644 --- a/src/systemd/sd-varlink.h +++ b/src/systemd/sd-varlink.h @@ -96,6 +96,8 @@ sd_varlink* sd_varlink_ref(sd_varlink *link); sd_varlink* sd_varlink_unref(sd_varlink *v); int sd_varlink_get_fd(sd_varlink *v); +int sd_varlink_get_input_fd(sd_varlink *v); +int sd_varlink_get_output_fd(sd_varlink *v); int sd_varlink_get_events(sd_varlink *v); int sd_varlink_get_timeout(sd_varlink *v, uint64_t *ret); diff --git a/src/test/test-ask-password-api.c b/src/test/test-ask-password-api.c index e58e86870b..d79c476489 100644 --- a/src/test/test-ask-password-api.c +++ b/src/test/test-ask-password-api.c @@ -9,11 +9,14 @@ TEST(ask_password) { int r; static const AskPasswordRequest req = { + .tty_fd = -EBADF, .message = "hello?", .keyring = "da key", + .until = USEC_INFINITY, + .hup_fd = -EBADF, }; - r = ask_password_tty(-EBADF, &req, /* until= */ 0, /* flags= */ ASK_PASSWORD_CONSOLE_COLOR, /* flag_file= */ NULL, &ret); + r = ask_password_tty(&req, /* flags= */ ASK_PASSWORD_CONSOLE_COLOR, &ret); if (r == -ECANCELED) ASSERT_NULL(ret); else { diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index 182d59bd20..74c64d392a 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -11,6 +11,7 @@ #include "varlink-idl-util.h" #include "varlink-io.systemd.h" #include "varlink-io.systemd.BootControl.h" +#include "varlink-io.systemd.AskPassword.h" #include "varlink-io.systemd.Credentials.h" #include "varlink-io.systemd.Import.h" #include "varlink-io.systemd.Journal.h" @@ -194,6 +195,8 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd_MachineImage); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_AskPassword); + print_separator(); test_parse_format_one(&vl_interface_xyz_test); } diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 8b83212755..094810dcb3 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -21,6 +21,7 @@ #include "build.h" #include "conf-parser.h" #include "constants.h" +#include "daemon-util.h" #include "devnum-util.h" #include "dirent-util.h" #include "exit-status.h" @@ -147,10 +148,14 @@ static int agent_ask_password_tty( } AskPasswordRequest req = { + .tty_fd = tty_fd, .message = message, + .flag_file = flag_file, + .until = until, + .hup_fd = -EBADF, }; - r = ask_password_tty(tty_fd, &req, until, flags, flag_file, ret); + r = ask_password_tty(&req, flags, ret); if (arg_console) { assert(tty_fd >= 0); @@ -243,20 +248,32 @@ static int process_one_password_file(const char *filename, FILE *f) { SET_FLAG(flags, ASK_PASSWORD_ECHO, echo); SET_FLAG(flags, ASK_PASSWORD_SILENT, silent); - if (arg_plymouth) { - AskPasswordRequest req = { - .message = message, - }; + /* Allow providing a password via env var, for debugging purposes */ + const char *e = secure_getenv("SYSTEMD_ASK_PASSWORD_AGENT_PASSWORD"); + if (e) { + passwords = strv_new(e); + if (!passwords) + return log_oom(); + } else { + if (arg_plymouth) { + AskPasswordRequest req = { + .tty_fd = -EBADF, + .message = message, + .flag_file = filename, + .until = not_after, + .hup_fd = -EBADF, + }; - r = ask_password_plymouth(&req, not_after, flags, filename, &passwords); - } else - r = agent_ask_password_tty(message, not_after, flags, filename, &passwords); - if (r < 0) { - /* If the query went away, that's OK */ - if (IN_SET(r, -ETIME, -ENOENT)) - return 0; + r = ask_password_plymouth(&req, flags, &passwords); + } else + r = agent_ask_password_tty(message, not_after, flags, filename, &passwords); + if (r < 0) { + /* If the query went away, that's OK */ + if (IN_SET(r, -ETIME, -ENOENT)) + return 0; - return log_error_errno(r, "Failed to query password: %m"); + return log_error_errno(r, "Failed to query password: %m"); + } } assert(!strv_isempty(passwords)); @@ -385,6 +402,9 @@ static int process_and_watch_password_files(bool watch) { pollfd[FD_INOTIFY] = (struct pollfd) { .fd = notify, .events = POLLIN }; } + _unused_ _cleanup_(notify_on_cleanup) const char *notify_stop = + notify_start(NOTIFY_READY, NOTIFY_STOPPING); + for (;;) { usec_t timeout = USEC_INFINITY; diff --git a/test/units/TEST-74-AUX-UTILS.ask-password.sh b/test/units/TEST-74-AUX-UTILS.ask-password.sh new file mode 100755 index 0000000000..1ae1df1142 --- /dev/null +++ b/test/units/TEST-74-AUX-UTILS.ask-password.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +# shellcheck source=test/units/util.sh +. "$(dirname "$0")"/util.sh + +at_exit() { + set +e + systemctl stop waldo-ask-pw-agent.service +} + +trap at_exit EXIT + +systemd-ask-password --help +systemd-tty-ask-password-agent --list + +varlinkctl introspect /run/systemd/io.systemd.AskPassword + +# Spawn an agent that always replies all ask password requests with "waldo" +systemd-run -u waldo-ask-pw-agent.service -p Environment=SYSTEMD_ASK_PASSWORD_AGENT_PASSWORD=waldo -p Type=notify /usr/bin/systemd-tty-ask-password-agent --watch --console=/dev/console +assert_eq "$(systemd-ask-password --no-tty)" "waldo" +assert_eq "$(varlinkctl call /usr/bin/systemd-ask-password io.systemd.AskPassword.Ask '{"message":"foobar"}' | jq '.passwords[0]')" "\"waldo\"" diff --git a/units/meson.build b/units/meson.build index 00978a18b0..f6d661da97 100644 --- a/units/meson.build +++ b/units/meson.build @@ -245,6 +245,11 @@ units = [ { 'file' : 'system-update-cleanup.service' }, { 'file' : 'system-update-pre.target' }, { 'file' : 'system-update.target' }, + { + 'file' : 'systemd-ask-password.socket', + 'symlinks' : ['sockets.target.wants/'] + }, + { 'file' : 'systemd-ask-password@.service' }, { 'file' : 'systemd-ask-password-console.path', 'symlinks' : ['sysinit.target.wants/'], diff --git a/units/systemd-ask-password-console.service b/units/systemd-ask-password-console.service index afd0f0baad..b8066da851 100644 --- a/units/systemd-ask-password-console.service +++ b/units/systemd-ask-password-console.service @@ -22,4 +22,5 @@ Before=shutdown.target initrd-switch-root.target [Service] ExecStart=systemd-tty-ask-password-agent --watch --console +Type=notify SystemCallArchitectures=native diff --git a/units/systemd-ask-password-wall.service b/units/systemd-ask-password-wall.service index 18b59d99a7..c785614e1e 100644 --- a/units/systemd-ask-password-wall.service +++ b/units/systemd-ask-password-wall.service @@ -15,4 +15,5 @@ After=systemd-user-sessions.service [Service] ExecStartPre=-systemctl stop systemd-ask-password-console.path systemd-ask-password-console.service systemd-ask-password-plymouth.path systemd-ask-password-plymouth.service ExecStart=systemd-tty-ask-password-agent --wall +Type=notify SystemCallArchitectures=native diff --git a/units/systemd-ask-password.socket b/units/systemd-ask-password.socket new file mode 100644 index 0000000000..c8d203b077 --- /dev/null +++ b/units/systemd-ask-password.socket @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Query the User Interactively for a Password +Documentation=man:systemd-ask-password(1) +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/io.systemd.AskPassword +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +MaxConnectionsPerSource=16 diff --git a/units/systemd-ask-password@.service b/units/systemd-ask-password@.service new file mode 100644 index 0000000000..07bbd45a7b --- /dev/null +++ b/units/systemd-ask-password@.service @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Query the User Interactively for a Password +Documentation=man:systemd-ask-password(1) +DefaultDependencies=no + +[Service] +ExecStart=-systemd-ask-password --system --no-tty diff --git a/units/user/meson.build b/units/user/meson.build index 21070f7ef8..290f268ef1 100644 --- a/units/user/meson.build +++ b/units/user/meson.build @@ -1,34 +1,64 @@ # SPDX-License-Identifier: LGPL-2.1-or-later units = [ - 'app.slice', - 'background.slice', - 'basic.target', - 'bluetooth.target', - 'capsule@.target', - 'default.target', - 'exit.target', - 'graphical-session-pre.target', - 'graphical-session.target', - 'paths.target', - 'printer.target', - 'session.slice', - 'shutdown.target', - 'smartcard.target', - 'sockets.target', - 'sound.target', - 'systemd-exit.service', - 'systemd-tmpfiles-clean.service', - 'systemd-tmpfiles-clean.timer', - 'systemd-tmpfiles-setup.service', - 'timers.target', + { 'file' : 'app.slice' }, + { 'file' : 'background.slice' }, + { 'file' : 'basic.target' }, + { 'file' : 'bluetooth.target' }, + { 'file' : 'capsule@.target' }, + { 'file' : 'default.target' }, + { 'file' : 'exit.target' }, + { 'file' : 'graphical-session-pre.target' }, + { 'file' : 'graphical-session.target' }, + { 'file' : 'paths.target' }, + { 'file' : 'printer.target' }, + { 'file' : 'session.slice' }, + { 'file' : 'shutdown.target' }, + { 'file' : 'smartcard.target' }, + { 'file' : 'sockets.target' }, + { 'file' : 'sound.target' }, + { + 'file' : 'systemd-ask-password.socket', + 'symlinks' : ['sockets.target.wants/'] + }, + { 'file' : 'systemd-ask-password@.service' }, + { 'file' : 'systemd-exit.service' }, + { 'file' : 'systemd-tmpfiles-clean.service' }, + { 'file' : 'systemd-tmpfiles-clean.timer' }, + { 'file' : 'systemd-tmpfiles-setup.service' }, + { 'file' : 'timers.target' }, + { + 'file' : 'xdg-desktop-autostart.target', + 'conditions': ['ENABLE_XDG_AUTOSTART'], + } ] -if conf.get('ENABLE_XDG_AUTOSTART') == 1 - units += 'xdg-desktop-autostart.target' -endif +foreach unit : units + file = unit.get('file') -foreach file : units - install_data(file, - install_dir : userunitdir) + install = true + foreach cond : unit.get('conditions', []) + if conf.get(cond) != 1 + install = false + break + endif + endforeach + + if install + install_data(file, + install_dir : userunitdir) + + foreach target : unit.get('symlinks', []) + if target.endswith('/') + install_emptydir(userunitdir / target) + meson.add_install_script(sh, '-c', + ln_s.format(userunitdir / file, + userunitdir / target / file)) + else + meson.add_install_script(sh, '-c', + ln_s.format(userunitdir / file, + userunitdir / target)) + endif + endforeach + endif endforeach diff --git a/units/user/systemd-ask-password.socket b/units/user/systemd-ask-password.socket new file mode 100644 index 0000000000..ce9ccd9e55 --- /dev/null +++ b/units/user/systemd-ask-password.socket @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Query the User Interactively for a Password +Documentation=man:systemd-ask-password(1) +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=%t/systemd/io.systemd.AskPassword +FileDescriptorName=varlink +SocketMode=0600 +Accept=yes diff --git a/units/user/systemd-ask-password@.service b/units/user/systemd-ask-password@.service new file mode 100644 index 0000000000..e5d72ff343 --- /dev/null +++ b/units/user/systemd-ask-password@.service @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Query the User Interactively for a Password +Documentation=man:systemd-ask-password(1) +DefaultDependencies=no + +[Service] +ExecStart=-systemd-ask-password --user --no-tty