From 96cd7eb9b90a3c7f23e292c4b72bbc1121f207ca Mon Sep 17 00:00:00 2001 From: Phil Krylov Date: Sat, 19 Sep 2026 16:18:35 +0200 Subject: [PATCH 1/2] vfs/sftpfs: Fix keyboard-interactive auth Don't mix password auth and keyboard-interactive auth. Show all prompts verbatim to user. Don't save responses, as we can't differentiate between permanent passwords and one-time codes. Signed-off-by: Phil Krylov Co-authored-by: Yury V. Zaytsev --- src/vfs/sftpfs/config_parser.c | 9 +++ src/vfs/sftpfs/connection.c | 117 ++++++++++++++++----------------- src/vfs/sftpfs/internal.h | 3 +- 3 files changed, 69 insertions(+), 60 deletions(-) diff --git a/src/vfs/sftpfs/config_parser.c b/src/vfs/sftpfs/config_parser.c index c0eb2eb9f9..60b08000e0 100644 --- a/src/vfs/sftpfs/config_parser.c +++ b/src/vfs/sftpfs/config_parser.c @@ -57,6 +57,7 @@ typedef struct gboolean password_auth; // FALSE - no passwords allowed (default TRUE) gboolean identities_only; // TRUE - no ssh agent (default FALSE) gboolean pubkey_auth; // FALSE - disable public key authentication (default TRUE) + gboolean kbd_int_auth; // FALSE - disable keyboard interactive authentication (default TRUE) char *identity_file; // A file from which the user's DSA, ECDSA or DSA authentication identity // is read. } sftpfs_ssh_config_entity_t; @@ -104,6 +105,12 @@ static struct FILENAME, offsetof (sftpfs_ssh_config_entity_t, identity_file), }, + { + "^\\s*KbdInteractiveAuthentication\\s+(.*)$", + NULL, + BOOLEAN, + offsetof (sftpfs_ssh_config_entity_t, kbd_int_auth), + }, { "^\\s*Port\\s+(.*)$", NULL, @@ -356,6 +363,7 @@ sftpfs_get_config_entity (const vfs_path_element_t *vpath_element, GError **mcer config_entity->password_auth = TRUE; config_entity->identities_only = FALSE; config_entity->pubkey_auth = TRUE; + config_entity->kbd_int_auth = TRUE; config_entity->port = SFTP_DEFAULT_PORT; config_filename = sftpfs_correct_file_name (SFTPFS_SSH_CONFIG); @@ -415,6 +423,7 @@ sftpfs_fill_connection_data_from_config (struct vfs_s_super *super, GError **mce sftpfs_super->config_auth_type = (config_entity->pubkey_auth) ? PUBKEY : 0; sftpfs_super->config_auth_type |= (config_entity->identities_only) ? 0 : AGENT; sftpfs_super->config_auth_type |= (config_entity->password_auth) ? PASSWORD : 0; + sftpfs_super->config_auth_type |= (config_entity->kbd_int_auth) ? KEYBOARD_INTERACTIVE : 0; if (super->path_element->port == 0) super->path_element->port = config_entity->port; diff --git a/src/vfs/sftpfs/connection.c b/src/vfs/sftpfs/connection.c index a3afaef394..85e3cd87f5 100644 --- a/src/vfs/sftpfs/connection.c +++ b/src/vfs/sftpfs/connection.c @@ -113,9 +113,6 @@ static const char *default_hostkey_methods = * */ -static const char *kbi_passwd = NULL; -static const struct vfs_s_super *kbi_super = NULL; - /* --------------------------------------------------------------------------------------------- */ /*** file scope functions ************************************************************************/ /* --------------------------------------------------------------------------------------------- */ @@ -641,8 +638,7 @@ sftpfs_recognize_auth_types (struct vfs_s_super *super) if (userauthlist == NULL) return FALSE; - if ((strstr (userauthlist, "password") != NULL - || strstr (userauthlist, "keyboard-interactive") != NULL) + if (strstr (userauthlist, "password") != NULL && (sftpfs_super->config_auth_type & PASSWORD) != 0) sftpfs_super->auth_type |= PASSWORD; @@ -653,6 +649,10 @@ sftpfs_recognize_auth_types (struct vfs_s_super *super) if ((sftpfs_super->config_auth_type & AGENT) != 0) sftpfs_super->auth_type |= AGENT; + if (strstr (userauthlist, "keyboard-interactive") != NULL + && (sftpfs_super->config_auth_type & KEYBOARD_INTERACTIVE) != 0) + sftpfs_super->auth_type |= KEYBOARD_INTERACTIVE; + return TRUE; } @@ -762,13 +762,13 @@ sftpfs_open_connection_ssh_key (struct vfs_s_super *super, GError **mcerror) * Keyboard-interactive password helper for opening connection to host by * sftpfs_open_connection_ssh_password * - * Uses global kbi_super (data with existing connection) and kbi_passwd (password) + * Prompts the user for each server challenge and collects the response. * - * @param name username - * @param name_len length of @name - * @param instruction unused - * @param instruction_len unused - * @param num_prompts number of possible problems to process + * @param name username. It's usually NULL, though, so unused + * @param name_len length of @name. Unused + * @param instruction optional instruction + * @param instruction_len length of @instruction + * @param num_prompts number of possible prompts to process * @param prompts array of prompts to process * @param responses array of responses, one per prompt * @param abstract unused @@ -777,27 +777,58 @@ sftpfs_open_connection_ssh_key (struct vfs_s_super *super, GError **mcerror) static LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC (sftpfs_keyboard_interactive_helper) { int i; - size_t len; - (void) instruction; - (void) instruction_len; + (void) name; + (void) name_len; (void) abstract; - if (kbi_super == NULL || kbi_passwd == NULL) - return; - - if (strncmp (name, kbi_super->path_element->user, name_len) != 0) - return; + for (i = 0; i < num_prompts; ++i) + { + char *full_prompt; + char *passwd; - // assume these are password prompts - len = strlen (kbi_passwd); + full_prompt = (instruction_len > 0 && i == 0) + ? g_strdup_printf ("%.*s\n%.*s", instruction_len, instruction, (int) prompts[i].length, + prompts[i].text) + : g_strndup ((const char *) prompts[i].text, prompts[i].length); + passwd = input_dialog (_ ("SFTP authentication"), full_prompt, "mc.vfs.password", + prompts[i].echo != 0 ? "" : INPUT_PASSWORD, INPUT_COMPLETE_NONE); - for (i = 0; i < num_prompts; ++i) - if (memcmp (prompts[i].text, "Password: ", prompts[i].length) == 0) + if (passwd != NULL) { - responses[i].text = strdup (kbi_passwd); - responses[i].length = len; + // libssh2 is going to free(3) the text. + // Allocate it using libc's strdup(3) to stay on the safe side. + responses[i].text = strdup (passwd); + responses[i].length = strlen (passwd); + g_free (passwd); } + g_free (full_prompt); + } +} + +/** + * Open connection to host using keyboard-interactive auth. + * + * @param super connection data + * @param mcerror pointer to the error handler + * @return TRUE if connection was successfully opened, FALSE otherwise + */ + +static gboolean +sftpfs_open_connection_ssh_keyboard_interactive (struct vfs_s_super *super, GError **mcerror) +{ + sftpfs_super_t *sftpfs_super = SFTP_SUPER (super); + int rc; + + mc_return_val_if_error (mcerror, FALSE); + + if ((sftpfs_super->auth_type & KEYBOARD_INTERACTIVE) == 0) + return FALSE; + + rc = libssh2_userauth_keyboard_interactive (sftpfs_super->session, super->path_element->user, + sftpfs_keyboard_interactive_helper); + // A kbi auth failure is not fatal here; password auth may still succeed + return rc == 0; } /* --------------------------------------------------------------------------------------------- */ @@ -830,21 +861,6 @@ sftpfs_open_connection_ssh_password (struct vfs_s_super *super, GError **mcerror ; if (rc == 0) return TRUE; - - kbi_super = super; - kbi_passwd = super->path_element->password; - - while ((rc = libssh2_userauth_keyboard_interactive (sftpfs_super->session, - super->path_element->user, - sftpfs_keyboard_interactive_helper)) - == LIBSSH2_ERROR_EAGAIN) - ; - - kbi_super = NULL; - kbi_passwd = NULL; - - if (rc == 0) - return TRUE; } p = g_strdup_printf (_ ("sftp: Enter password for %s "), super->path_element->user); @@ -855,25 +871,7 @@ sftpfs_open_connection_ssh_password (struct vfs_s_super *super, GError **mcerror mc_propagate_error (mcerror, 0, "%s", _ ("sftp: Password is empty.")); else { - while ((rc = libssh2_userauth_password (sftpfs_super->session, super->path_element->user, - passwd)) - == LIBSSH2_ERROR_EAGAIN) - ; - - if (rc != 0) - { - kbi_super = super; - kbi_passwd = passwd; - - while ((rc = libssh2_userauth_keyboard_interactive (sftpfs_super->session, - super->path_element->user, - sftpfs_keyboard_interactive_helper)) - == LIBSSH2_ERROR_EAGAIN) - ; - - kbi_super = NULL; - kbi_passwd = NULL; - } + rc = libssh2_userauth_password (sftpfs_super->session, super->path_element->user, passwd); if (rc == 0) { @@ -950,6 +948,7 @@ sftpfs_open_connection (struct vfs_s_super *super, GError **mcerror) if (!sftpfs_open_connection_ssh_agent (super, mcerror) && !sftpfs_open_connection_ssh_key (super, mcerror) + && !sftpfs_open_connection_ssh_keyboard_interactive (super, mcerror) && !sftpfs_open_connection_ssh_password (super, mcerror)) return (-1); diff --git a/src/vfs/sftpfs/internal.h b/src/vfs/sftpfs/internal.h index aaf6d928d9..052ea8742c 100644 --- a/src/vfs/sftpfs/internal.h +++ b/src/vfs/sftpfs/internal.h @@ -30,7 +30,8 @@ typedef enum NONE = 0, PUBKEY = (1 << 0), PASSWORD = (1 << 1), - AGENT = (1 << 2) + AGENT = (1 << 2), + KEYBOARD_INTERACTIVE = (1 << 3), } sftpfs_auth_type_t; /*** structures declarations (and typedefs of structures)*****************************************/ From 3ca20d317ac04bd95651c6a524ccb272e817f0d8 Mon Sep 17 00:00:00 2001 From: Phil Krylov Date: Mon, 28 Sep 2026 19:56:54 +0200 Subject: [PATCH 2/2] vfs/sftpfs: Support deprecated ChallengeResponseAuthentication synonym for KbdInteractiveAuthentication Signed-off-by: Phil Krylov --- src/vfs/sftpfs/config_parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vfs/sftpfs/config_parser.c b/src/vfs/sftpfs/config_parser.c index 60b08000e0..0abffa0501 100644 --- a/src/vfs/sftpfs/config_parser.c +++ b/src/vfs/sftpfs/config_parser.c @@ -106,7 +106,7 @@ static struct offsetof (sftpfs_ssh_config_entity_t, identity_file), }, { - "^\\s*KbdInteractiveAuthentication\\s+(.*)$", + "^\\s*(?:KbdInteractiveAuthentication|ChallengeResponseAuthentication)\\s+(.*)$", NULL, BOOLEAN, offsetof (sftpfs_ssh_config_entity_t, kbd_int_auth),