/* * auth1.c - authentication with SSH protocols * * This file is part of the SSH Library * * Copyright (c) 2003-2008 by Aris Adamantiadis * Copyright (c) 2008-2009 Andreas Schneider * * The SSH Library 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. * * The SSH Library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the SSH Library; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ #include #include #include #ifndef _WIN32 #include #endif #include "libssh/priv.h" #include "libssh/ssh2.h" #include "libssh/buffer.h" #include "libssh/agent.h" #include "libssh/keyfiles.h" #include "libssh/misc.h" #include "libssh/packet.h" #include "libssh/session.h" #include "libssh/keys.h" #include "libssh/auth.h" /** * @defgroup libssh_auth The SSH authentication functions. * @ingroup libssh * * Functions to authenticate with a server. * * @{ */ /** * @internal * * @brief Ask access to the ssh-userauth service. * * @param[in] session The SSH session handle. * * @returns SSH_OK on success, SSH_ERROR on error. * * @bug current implementation is blocking */ static int ask_userauth(ssh_session session) { int rc = 0; enter_function(); do { rc=ssh_service_request(session,"ssh-userauth"); if(rc==SSH_AGAIN) ssh_handle_packets(session,-1); } while(rc==SSH_AGAIN); leave_function(); return rc; } /** * @internal * * @brief Handles a SSH_USERAUTH_BANNER packet. * * This banner should be shown to user prior to authentication */ SSH_PACKET_CALLBACK(ssh_packet_userauth_banner){ ssh_string banner; (void)type; (void)user; enter_function(); banner = buffer_get_ssh_string(packet); if (banner == NULL) { ssh_log(session, SSH_LOG_RARE, "Invalid SSH_USERAUTH_BANNER packet"); } else { ssh_log(session, SSH_LOG_PACKET, "Received SSH_USERAUTH_BANNER packet"); if(session->banner != NULL) ssh_string_free(session->banner); session->banner = banner; } leave_function(); return SSH_PACKET_USED; } /** * @internal * * @brief Handles a SSH_USERAUTH_FAILURE packet. * * This handles the complete or partial authentication failure. */ SSH_PACKET_CALLBACK(ssh_packet_userauth_failure){ char *auth_methods = NULL; ssh_string auth; uint8_t partial = 0; (void) type; (void) user; enter_function(); auth = buffer_get_ssh_string(packet); if (auth == NULL || buffer_get_u8(packet, &partial) != 1) { ssh_set_error(session, SSH_FATAL, "Invalid SSH_MSG_USERAUTH_FAILURE message"); session->auth_state=SSH_AUTH_STATE_ERROR; goto end; } auth_methods = ssh_string_to_char(auth); if (auth_methods == NULL) { ssh_set_error_oom(session); goto end; } if (partial) { session->auth_state=SSH_AUTH_STATE_PARTIAL; ssh_log(session,SSH_LOG_PROTOCOL, "Partial success. Authentication that can continue: %s", auth_methods); } else { session->auth_state=SSH_AUTH_STATE_FAILED; ssh_log(session, SSH_LOG_PROTOCOL, "Access denied. Authentication that can continue: %s", auth_methods); ssh_set_error(session, SSH_REQUEST_DENIED, "Access denied. Authentication that can continue: %s", auth_methods); session->auth_methods = 0; } if (strstr(auth_methods, "password") != NULL) { session->auth_methods |= SSH_AUTH_METHOD_PASSWORD; } if (strstr(auth_methods, "keyboard-interactive") != NULL) { session->auth_methods |= SSH_AUTH_METHOD_INTERACTIVE; } if (strstr(auth_methods, "publickey") != NULL) { session->auth_methods |= SSH_AUTH_METHOD_PUBLICKEY; } if (strstr(auth_methods, "hostbased") != NULL) { session->auth_methods |= SSH_AUTH_METHOD_HOSTBASED; } end: ssh_string_free(auth); SAFE_FREE(auth_methods); leave_function(); return SSH_PACKET_USED; } /** * @internal * * @brief Handles a SSH_USERAUTH_SUCCESS packet. * * It is also used to communicate the new to the upper levels. */ SSH_PACKET_CALLBACK(ssh_packet_userauth_success){ enter_function(); (void)packet; (void)type; (void)user; ssh_log(session,SSH_LOG_PACKET,"Received SSH_USERAUTH_SUCCESS"); ssh_log(session,SSH_LOG_PROTOCOL,"Authentication successful"); session->auth_state=SSH_AUTH_STATE_SUCCESS; session->session_state=SSH_SESSION_STATE_AUTHENTICATED; leave_function(); return SSH_PACKET_USED; } /** * @internal * * @brief Handles a SSH_USERAUTH_PK_OK or SSH_USERAUTH_INFO_REQUEST packet. * * Since the two types of packets share the same code, additional work is done * to understand if we are in a public key or keyboard-interactive context. */ SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok){ int rc; enter_function(); ssh_log(session,SSH_LOG_PACKET,"Received SSH_USERAUTH_PK_OK/INFO_REQUEST"); if(session->auth_state==SSH_AUTH_STATE_KBDINT_SENT){ /* Assuming we are in keyboard-interactive context */ ssh_log(session,SSH_LOG_PACKET,"keyboard-interactive context, assuming SSH_USERAUTH_INFO_REQUEST"); rc=ssh_packet_userauth_info_request(session,type,packet,user); } else { session->auth_state=SSH_AUTH_STATE_PK_OK; ssh_log(session,SSH_LOG_PACKET,"assuming SSH_USERAUTH_PK_OK"); rc=SSH_PACKET_USED; } leave_function(); return rc; } static int wait_auth_status(ssh_session session) { int rc = SSH_AUTH_ERROR; enter_function(); while (session->auth_state == SSH_AUTH_STATE_NONE || session->auth_state == SSH_AUTH_STATE_KBDINT_SENT) { if (ssh_handle_packets(session,-1) != SSH_OK) break; } switch(session->auth_state){ case SSH_AUTH_STATE_ERROR: rc=SSH_AUTH_ERROR; break; case SSH_AUTH_STATE_FAILED: rc=SSH_AUTH_DENIED; break; case SSH_AUTH_STATE_INFO: rc=SSH_AUTH_INFO; break; case SSH_AUTH_STATE_PARTIAL: rc=SSH_AUTH_PARTIAL; break; case SSH_AUTH_STATE_PK_OK: case SSH_AUTH_STATE_SUCCESS: rc=SSH_AUTH_SUCCESS; break; case SSH_AUTH_STATE_KBDINT_SENT: case SSH_AUTH_STATE_NONE: /* not reached */ rc=SSH_AUTH_ERROR; break; } leave_function(); return rc; } /** * @brief retrieves available authentication methods for this session * @obsolete * @see ssh_userauth_list */ int ssh_auth_list(ssh_session session) { return ssh_userauth_list(session, NULL); } /** * @brief retrieves available authentication methods for this session * @param[in] session the SSH session * @param[in] username set to NULL * @returns A bitfield of values SSH_AUTH_METHOD_NONE, SSH_AUTH_METHOD_PASSWORD, SSH_AUTH_METHOD_PUBLICKEY, SSH_AUTH_METHOD_HOSTBASED, SSH_AUTH_METHOD_INTERACTIVE. @warning Other reserved flags may appear in future versions. */ int ssh_userauth_list(ssh_session session, const char *username) { if (session == NULL) { return SSH_AUTH_ERROR; } #ifdef WITH_SSH1 if(session->version==1){ return SSH_AUTH_METHOD_PASSWORD; } #endif if (session->auth_methods == 0) { ssh_userauth_none(session, username); } return session->auth_methods; } /* use the "none" authentication question */ /** * @brief Try to authenticate through the "none" method. * * @param[in] session The ssh session to use. * * @param[in] username Deprecated, set to NULL. * * @returns SSH_AUTH_ERROR: A serious error happened.\n * SSH_AUTH_DENIED: Authentication failed: use another method\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method\n * SSH_AUTH_SUCCESS: Authentication success */ int ssh_userauth_none(ssh_session session, const char *username) { ssh_string user = NULL; ssh_string service = NULL; ssh_string method = NULL; int rc = SSH_AUTH_ERROR; enter_function(); #ifdef WITH_SSH1 if (session->version == 1) { rc = ssh_userauth1_none(session, username); leave_function(); return rc; } #endif if(session->auth_methods != 0){ /* userauth_none or other method was already tried before */ ssh_set_error(session,SSH_REQUEST_DENIED,"None method rejected by server"); leave_function(); return SSH_AUTH_DENIED; } if (username == NULL) { if (session->username == NULL) { if (ssh_options_apply(session) < 0) { leave_function(); return rc; } } user = ssh_string_from_char(session->username); } else { user = ssh_string_from_char(username); } if (user == NULL) { leave_function(); return rc; } if (ask_userauth(session) < 0) { ssh_string_free(user); leave_function(); return rc; } method = ssh_string_from_char("none"); if (method == NULL) { goto error; } service = ssh_string_from_char("ssh-connection"); if (service == NULL) { goto error; } if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST) < 0 || buffer_add_ssh_string(session->out_buffer, user) < 0 || buffer_add_ssh_string(session->out_buffer, service) < 0 || buffer_add_ssh_string(session->out_buffer, method) < 0) { goto error; } ssh_string_free(service); ssh_string_free(method); ssh_string_free(user); session->auth_state=SSH_AUTH_STATE_NONE; if (packet_send(session) == SSH_ERROR) { leave_function(); return rc; } rc = wait_auth_status(session); leave_function(); return rc; error: buffer_reinit(session->out_buffer); ssh_string_free(service); ssh_string_free(method); ssh_string_free(user); leave_function(); return rc; } /** * @brief Try to authenticate through public key. * * @param[in] session The ssh session to use. * * @param[in] username The username to authenticate. You can specify NULL if * ssh_option_set_username() has been used. You cannot try * two different logins in a row. * * @param[in] type The type of the public key. This value is given by * publickey_from_file() or ssh_privatekey_type(). * * @param[in] publickey A public key returned by publickey_from_file(). * * @returns SSH_AUTH_ERROR: A serious error happened.\n * SSH_AUTH_DENIED: The server doesn't accept that public key as an * authentication token. Try another key or another * method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use * ssh_userauth_pubkey(). * * @see publickey_from_file() * @see privatekey_from_file() * @see ssh_privatekey_type() * @see ssh_userauth_pubkey() */ int ssh_userauth_offer_pubkey(ssh_session session, const char *username, int type, ssh_string publickey) { ssh_string user = NULL; ssh_string service = NULL; ssh_string method = NULL; ssh_string algo = NULL; int rc = SSH_AUTH_ERROR; enter_function(); #ifdef WITH_SSH1 if (session->version == 1) { ssh_userauth1_offer_pubkey(session, username, type, publickey); leave_function(); return rc; } #endif if (username == NULL) { if (session->username == NULL) { if (ssh_options_apply(session) < 0) { leave_function(); return rc; } } user = ssh_string_from_char(session->username); } else { user = ssh_string_from_char(username); } if (user == NULL) { leave_function(); return rc; } if (ask_userauth(session) < 0) { ssh_string_free(user); leave_function(); return rc; } service = ssh_string_from_char("ssh-connection"); if (service == NULL) { goto error; } method = ssh_string_from_char("publickey"); if (method == NULL) { goto error; } algo = ssh_string_from_char(ssh_type_to_char(type)); if (algo == NULL) { goto error; } if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST) < 0 || buffer_add_ssh_string(session->out_buffer, user) < 0 || buffer_add_ssh_string(session->out_buffer, service) < 0 || buffer_add_ssh_string(session->out_buffer, method) < 0 || buffer_add_u8(session->out_buffer, 0) < 0 || buffer_add_ssh_string(session->out_buffer, algo) < 0 || buffer_add_ssh_string(session->out_buffer, publickey) < 0) { goto error; } ssh_string_free(user); ssh_string_free(method); ssh_string_free(service); ssh_string_free(algo); session->auth_state=SSH_AUTH_STATE_NONE; if (packet_send(session) == SSH_ERROR) { leave_function(); return rc; } rc = wait_auth_status(session); leave_function(); return rc; error: buffer_reinit(session->out_buffer); ssh_string_free(user); ssh_string_free(method); ssh_string_free(service); ssh_string_free(algo); leave_function(); return rc; } /** * @brief Try to authenticate through public key. * * @param[in] session The ssh session to use. * * @param[in] username The username to authenticate. You can specify NULL if * ssh_option_set_username() has been used. You cannot try * two different logins in a row. * * @param[in] publickey A public key returned by publickey_from_file(), or NULL * to generate automatically from privatekey. * * @param[in] privatekey A private key returned by privatekey_from_file(). * * @returns SSH_AUTH_ERROR: A serious error happened.\n * SSH_AUTH_DENIED: Authentication failed: use another method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n * SSH_AUTH_SUCCESS: Authentication successful. * * @see publickey_from_file() * @see privatekey_from_file() * @see privatekey_free() * @see ssh_userauth_offer_pubkey() */ int ssh_userauth_pubkey(ssh_session session, const char *username, ssh_string publickey, ssh_private_key privatekey) { ssh_string user = NULL; ssh_string service = NULL; ssh_string method = NULL; ssh_string algo = NULL; ssh_string sign = NULL; ssh_public_key pk = NULL; ssh_string pkstr = NULL; int rc = SSH_AUTH_ERROR; enter_function(); #if 0 if (session->version == 1) { return ssh_userauth1_pubkey(session, username, publickey, privatekey); } #endif if (username == NULL) { if (session->username == NULL) { if (ssh_options_apply(session) < 0) { leave_function(); return rc; } } user = ssh_string_from_char(session->username); } else { user = ssh_string_from_char(username); } if (user == NULL) { leave_function(); return rc; } if (ask_userauth(session) < 0) { ssh_string_free(user); leave_function(); return rc; } service = ssh_string_from_char("ssh-connection"); if (service == NULL) { goto error; } method = ssh_string_from_char("publickey"); if (method == NULL) { goto error; } algo = ssh_string_from_char(ssh_type_to_char(privatekey->type)); if (algo == NULL) { goto error; } if (publickey == NULL) { pk = publickey_from_privatekey(privatekey); if (pk == NULL) { goto error; } pkstr = publickey_to_string(pk); publickey_free(pk); if (pkstr == NULL) { goto error; } } /* we said previously the public key was accepted */ if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST) < 0 || buffer_add_ssh_string(session->out_buffer, user) < 0 || buffer_add_ssh_string(session->out_buffer, service) < 0 || buffer_add_ssh_string(session->out_buffer, method) < 0 || buffer_add_u8(session->out_buffer, 1) < 0 || buffer_add_ssh_string(session->out_buffer, algo) < 0 || buffer_add_ssh_string(session->out_buffer, (publickey == NULL ? pkstr : publickey)) < 0) { goto error; } ssh_string_free(user); ssh_string_free(service); ssh_string_free(method); ssh_string_free(algo); ssh_string_free(pkstr); sign = ssh_do_sign(session,session->out_buffer, privatekey); if (sign) { if (buffer_add_ssh_string(session->out_buffer,sign) < 0) { goto error; } ssh_string_free(sign); session->auth_state=SSH_AUTH_STATE_NONE; if (packet_send(session) == SSH_ERROR) { leave_function(); return rc; } rc = wait_auth_status(session); } leave_function(); return rc; error: buffer_reinit(session->out_buffer); ssh_string_free(user); ssh_string_free(service); ssh_string_free(method); ssh_string_free(algo); ssh_string_free(pkstr); leave_function(); return rc; } /** * @brief Try to authenticate through a private key file. * * @param[in] session The ssh session to use. * * @param[in] username The username to authenticate. You can specify NULL if * ssh_option_set_username() has been used. You cannot try * two different logins in a row. * * @param[in] filename Filename containing the private key. * * @param[in] passphrase Passphrase to decrypt the private key. Set to null if * none is needed or it is unknown. * * @returns SSH_AUTH_ERROR: A serious error happened.\n * SSH_AUTH_DENIED: Authentication failed: use another method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n * SSH_AUTH_SUCCESS: Authentication successful. * * @see publickey_from_file() * @see privatekey_from_file() * @see privatekey_free() * @see ssh_userauth_pubkey() */ int ssh_userauth_privatekey_file(ssh_session session, const char *username, const char *filename, const char *passphrase) { char *pubkeyfile = NULL; ssh_string pubkey = NULL; ssh_private_key privkey = NULL; int type = 0; int rc = SSH_AUTH_ERROR; enter_function(); pubkeyfile = malloc(strlen(filename) + 1 + 4); if (pubkeyfile == NULL) { leave_function(); return SSH_AUTH_ERROR; } sprintf(pubkeyfile, "%s.pub", filename); pubkey = publickey_from_file(session, pubkeyfile, &type); if (pubkey == NULL) { ssh_log(session, SSH_LOG_RARE, "Public key file %s not found. Trying to generate it.", pubkeyfile); /* auto-detect the key type with type=0 */ privkey = privatekey_from_file(session, filename, 0, passphrase); } else { ssh_log(session, SSH_LOG_RARE, "Public key file %s loaded.", pubkeyfile); privkey = privatekey_from_file(session, filename, type, passphrase); } if (privkey == NULL) { goto error; } /* ssh_userauth_pubkey is responsible for taking care of null-pubkey */ rc = ssh_userauth_pubkey(session, username, pubkey, privkey); privatekey_free(privkey); error: SAFE_FREE(pubkeyfile); ssh_string_free(pubkey); leave_function(); return rc; } #ifndef _WIN32 /** * @brief Try to authenticate through public key with an ssh agent. * * @param[in] session The ssh session to use. * * @param[in] username The username to authenticate. You can specify NULL if * ssh_option_set_username() has been used. You cannot try * two different logins in a row. * * @param[in] publickey The public key provided by the agent. * * @returns SSH_AUTH_ERROR: A serious error happened.\n * SSH_AUTH_DENIED: Authentication failed: use another method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n * SSH_AUTH_SUCCESS: Authentication successful. * * @see publickey_from_file() * @see privatekey_from_file() * @see privatekey_free() * @see ssh_userauth_offer_pubkey() */ int ssh_userauth_agent_pubkey(ssh_session session, const char *username, ssh_public_key publickey) { ssh_string user = NULL; ssh_string service = NULL; ssh_string method = NULL; ssh_string algo = NULL; ssh_string key = NULL; ssh_string sign = NULL; int rc = SSH_AUTH_ERROR; enter_function(); if (! agent_is_running(session)) { return rc; } if (username == NULL) { if (session->username == NULL) { if (ssh_options_apply(session) < 0) { leave_function(); return rc; } } user = ssh_string_from_char(session->username); } else { user = ssh_string_from_char(username); } if (user == NULL) { leave_function(); return rc; } if (ask_userauth(session) < 0) { ssh_string_free(user); leave_function(); return rc; } service = ssh_string_from_char("ssh-connection"); if (service == NULL) { goto error; } method = ssh_string_from_char("publickey"); if (method == NULL) { goto error; } algo = ssh_string_from_char(ssh_type_to_char(publickey->type)); if (algo == NULL) { goto error; } key = publickey_to_string(publickey); if (key == NULL) { goto error; } /* we said previously the public key was accepted */ if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST) < 0 || buffer_add_ssh_string(session->out_buffer, user) < 0 || buffer_add_ssh_string(session->out_buffer, service) < 0 || buffer_add_ssh_string(session->out_buffer, method) < 0 || buffer_add_u8(session->out_buffer, 1) < 0 || buffer_add_ssh_string(session->out_buffer, algo) < 0 || buffer_add_ssh_string(session->out_buffer, key) < 0) { goto error; } sign = ssh_do_sign_with_agent(session, session->out_buffer, publickey); if (sign) { if (buffer_add_ssh_string(session->out_buffer, sign) < 0) { goto error; } ssh_string_free(sign); session->auth_state=SSH_AUTH_STATE_NONE; if (packet_send(session) == SSH_ERROR) { leave_function(); return rc; } rc = wait_auth_status(session); } ssh_string_free(user); ssh_string_free(service); ssh_string_free(method); ssh_string_free(algo); ssh_string_free(key); leave_function(); return rc; error: buffer_reinit(session->out_buffer); ssh_string_free(sign); ssh_string_free(user); ssh_string_free(service); ssh_string_free(method); ssh_string_free(algo); ssh_string_free(key); leave_function(); return rc; } #endif /* _WIN32 */ /** * @brief Try to authenticate by password. * * @param[in] session The ssh session to use. * * @param[in] username The username to authenticate. You can specify NULL if * ssh_option_set_username() has been used. You cannot try * two different logins in a row. * * @param[in] password The password to use. Take care to clean it after * the authentication. * * @returns SSH_AUTH_ERROR: A serious error happened.\n * SSH_AUTH_DENIED: Authentication failed: use another method.\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method.\n * SSH_AUTH_SUCCESS: Authentication successful. * * @see ssh_userauth_kbdint() * @see BURN_STRING */ int ssh_userauth_password(ssh_session session, const char *username, const char *password) { ssh_string user = NULL; ssh_string service = NULL; ssh_string method = NULL; ssh_string pwd = NULL; int rc = SSH_AUTH_ERROR; enter_function(); #ifdef WITH_SSH1 if (session->version == 1) { rc = ssh_userauth1_password(session, username, password); leave_function(); return rc; } #endif if (username == NULL) { if (session->username == NULL) { if (ssh_options_apply(session) < 0) { leave_function(); return rc; } } user = ssh_string_from_char(session->username); } else { user = ssh_string_from_char(username); } if (user == NULL) { leave_function(); return rc; } if (ask_userauth(session) < 0) { ssh_string_free(user); leave_function(); return rc; } service = ssh_string_from_char("ssh-connection"); if (service == NULL) { goto error; } method = ssh_string_from_char("password"); if (method == NULL) { goto error; } pwd = ssh_string_from_char(password); if (pwd == NULL) { goto error; } if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST) < 0 || buffer_add_ssh_string(session->out_buffer, user) < 0 || buffer_add_ssh_string(session->out_buffer, service) < 0 || buffer_add_ssh_string(session->out_buffer, method) < 0 || buffer_add_u8(session->out_buffer, 0) < 0 || buffer_add_ssh_string(session->out_buffer, pwd) < 0) { goto error; } ssh_string_free(user); ssh_string_free(service); ssh_string_free(method); ssh_string_burn(pwd); ssh_string_free(pwd); session->auth_state=SSH_AUTH_STATE_NONE; if (packet_send(session) == SSH_ERROR) { leave_function(); return rc; } rc = wait_auth_status(session); leave_function(); return rc; error: buffer_reinit(session->out_buffer); ssh_string_free(user); ssh_string_free(service); ssh_string_free(method); ssh_string_burn(pwd); ssh_string_free(pwd); leave_function(); return rc; } /** * @brief Tries to automatically authenticate with public key and "none" * * It may fail, for instance it doesn't ask for a password and uses a default * asker for passphrases (in case the private key is encrypted). * * @param[in] session The ssh session to authenticate with. * * @param[in] passphrase Use this passphrase to unlock the privatekey. Use NULL * if you don't want to use a passphrase or the user * should be asked. * * @returns SSH_AUTH_ERROR: A serious error happened\n * SSH_AUTH_DENIED: Authentication failed: use another method\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method\n * SSH_AUTH_SUCCESS: Authentication success * * @see ssh_userauth_kbdint() * @see ssh_userauth_password() */ int ssh_userauth_autopubkey(ssh_session session, const char *passphrase) { struct ssh_iterator *it; ssh_private_key privkey; ssh_public_key pubkey; ssh_string pubkey_string; int type = 0; int rc; enter_function(); /* Always test none authentication */ rc = ssh_userauth_none(session, NULL); if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_SUCCESS) { leave_function(); return rc; } /* Try authentication with ssh-agent first */ #ifndef _WIN32 if (agent_is_running(session)) { char *privkey_file = NULL; ssh_log(session, SSH_LOG_RARE, "Trying to authenticate with SSH agent keys as user: %s", session->username); for (pubkey = agent_get_first_ident(session, &privkey_file); pubkey != NULL; pubkey = agent_get_next_ident(session, &privkey_file)) { ssh_log(session, SSH_LOG_RARE, "Trying identity %s", privkey_file); pubkey_string = publickey_to_string(pubkey); if (pubkey_string) { rc = ssh_userauth_offer_pubkey(session, NULL, pubkey->type, pubkey_string); ssh_string_free(pubkey_string); if (rc == SSH_AUTH_ERROR) { SAFE_FREE(privkey_file); publickey_free(pubkey); leave_function(); return rc; } else if (rc != SSH_AUTH_SUCCESS) { ssh_log(session, SSH_LOG_PROTOCOL, "Public key refused by server"); SAFE_FREE(privkey_file); publickey_free(pubkey); continue; } ssh_log(session, SSH_LOG_PROTOCOL, "Public key accepted"); /* pubkey accepted by server ! */ rc = ssh_userauth_agent_pubkey(session, NULL, pubkey); if (rc == SSH_AUTH_ERROR) { SAFE_FREE(privkey_file); publickey_free(pubkey); leave_function(); return rc; } else if (rc != SSH_AUTH_SUCCESS) { ssh_log(session, SSH_LOG_RARE, "Server accepted public key but refused the signature ;" " It might be a bug of libssh"); SAFE_FREE(privkey_file); publickey_free(pubkey); continue; } /* auth success */ ssh_log(session, SSH_LOG_PROTOCOL, "Authentication using %s success", privkey_file); SAFE_FREE(privkey_file); publickey_free(pubkey); leave_function(); return SSH_AUTH_SUCCESS; } /* if pubkey */ SAFE_FREE(privkey_file); publickey_free(pubkey); } /* for each privkey */ } /* if agent is running */ #endif for (it = ssh_list_get_iterator(session->identity); it != NULL; it = it->next) { const char *privkey_file = it->data; int privkey_open = 0; privkey = NULL; ssh_log(session, SSH_LOG_PROTOCOL, "Trying to read privatekey %s", privkey_file); rc = ssh_try_publickey_from_file(session, privkey_file, &pubkey_string, &type); if (rc == 1) { char *publickey_file; size_t len; privkey = privatekey_from_file(session, privkey_file, type, passphrase); if (privkey == NULL) { ssh_log(session, SSH_LOG_RARE, "Reading private key %s failed (bad passphrase ?)", privkey_file); leave_function(); return SSH_AUTH_ERROR; } privkey_open = 1; pubkey = publickey_from_privatekey(privkey); if (pubkey == NULL) { privatekey_free(privkey); ssh_set_error_oom(session); leave_function(); return SSH_AUTH_ERROR; } pubkey_string = publickey_to_string(pubkey); type = pubkey->type; publickey_free(pubkey); if (pubkey_string == NULL) { ssh_set_error_oom(session); leave_function(); return SSH_AUTH_ERROR; } len = strlen(privkey_file) + 5; publickey_file = malloc(len); if (publickey_file == NULL) { ssh_set_error_oom(session); leave_function(); return SSH_AUTH_ERROR; } snprintf(publickey_file, len, "%s.pub", privkey_file); rc = ssh_publickey_to_file(session, publickey_file, pubkey_string, type); if (rc < 0) { ssh_log(session, SSH_LOG_PACKET, "Could not write public key to file: %s", publickey_file); } SAFE_FREE(publickey_file); } else if (rc < 0) { continue; } rc = ssh_userauth_offer_pubkey(session, NULL, type, pubkey_string); if (rc == SSH_AUTH_ERROR){ ssh_string_free(pubkey_string); ssh_log(session, SSH_LOG_RARE, "Publickey authentication error"); leave_function(); return rc; } else { if (rc != SSH_AUTH_SUCCESS){ ssh_log(session, SSH_LOG_PROTOCOL, "Publickey refused by server"); ssh_string_free(pubkey_string); continue; } } /* Public key accepted by server! */ if (!privkey_open) { ssh_log(session, SSH_LOG_PROTOCOL, "Trying to read privatekey %s", privkey_file); privkey = privatekey_from_file(session, privkey_file, type, passphrase); if (privkey == NULL) { ssh_log(session, SSH_LOG_RARE, "Reading private key %s failed (bad passphrase ?)", privkey_file); ssh_string_free(pubkey_string); continue; /* continue the loop with other pubkey */ } } rc = ssh_userauth_pubkey(session, NULL, pubkey_string, privkey); if (rc == SSH_AUTH_ERROR) { ssh_string_free(pubkey_string); privatekey_free(privkey); leave_function(); return rc; } else { if (rc != SSH_AUTH_SUCCESS){ ssh_log(session, SSH_LOG_RARE, "The server accepted the public key but refused the signature"); ssh_string_free(pubkey_string); privatekey_free(privkey); continue; } } /* auth success */ ssh_log(session, SSH_LOG_PROTOCOL, "Successfully authenticated using %s", privkey_file); ssh_string_free(pubkey_string); privatekey_free(privkey); leave_function(); return SSH_AUTH_SUCCESS; } /* at this point, pubkey is NULL and so is privkeyfile */ ssh_log(session, SSH_LOG_PROTOCOL, "Tried every public key, none matched"); ssh_set_error(session,SSH_NO_ERROR,"No public key matched"); leave_function(); return SSH_AUTH_DENIED; } struct ssh_kbdint_struct { uint32_t nprompts; char *name; char *instruction; char **prompts; unsigned char *echo; /* bool array */ char **answers; }; static ssh_kbdint kbdint_new(void) { ssh_kbdint kbd; kbd = malloc(sizeof (struct ssh_kbdint_struct)); if (kbd == NULL) { return NULL; } ZERO_STRUCTP(kbd); return kbd; } static void kbdint_free(ssh_kbdint kbd) { int i, n; if (kbd == NULL) { return; } n = kbd->nprompts; SAFE_FREE(kbd->name); SAFE_FREE(kbd->instruction); SAFE_FREE(kbd->echo); if (kbd->prompts) { for (i = 0; i < n; i++) { BURN_STRING(kbd->prompts[i]); SAFE_FREE(kbd->prompts[i]); } SAFE_FREE(kbd->prompts); } if (kbd->answers) { for (i = 0; i < n; i++) { BURN_STRING(kbd->answers[i]); SAFE_FREE(kbd->answers[i]); } SAFE_FREE(kbd->answers); } SAFE_FREE(kbd); } static void kbdint_clean(ssh_kbdint kbd) { int i, n; if (kbd == NULL) { return; } n = kbd->nprompts; SAFE_FREE(kbd->name); SAFE_FREE(kbd->instruction); SAFE_FREE(kbd->echo); if (kbd->prompts) { for (i = 0; i < n; i++) { BURN_STRING(kbd->prompts[i]); SAFE_FREE(kbd->prompts[i]); } SAFE_FREE(kbd->prompts); } if (kbd->answers) { for (i = 0; i < n; i++) { BURN_STRING(kbd->answers[i]); SAFE_FREE(kbd->answers[i]); } SAFE_FREE(kbd->answers); } kbd->nprompts = 0; } /* this function sends the first packet as explained in section 3.1 * of the draft */ static int kbdauth_init(ssh_session session, const char *user, const char *submethods) { ssh_string usr = NULL; ssh_string sub = NULL; ssh_string service = NULL; ssh_string method = NULL; int rc = SSH_AUTH_ERROR; enter_function(); usr = ssh_string_from_char(user); if (usr == NULL) { goto error; } sub = (submethods ? ssh_string_from_char(submethods) : ssh_string_from_char("")); if (sub == NULL) { goto error; } service = ssh_string_from_char("ssh-connection"); if (service == NULL) { goto error; } method = ssh_string_from_char("keyboard-interactive"); if (method == NULL) { goto error; } if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_REQUEST) < 0 || buffer_add_ssh_string(session->out_buffer, usr) < 0 || buffer_add_ssh_string(session->out_buffer, service) < 0 || buffer_add_ssh_string(session->out_buffer, method) < 0 || buffer_add_u32(session->out_buffer, 0) < 0 || buffer_add_ssh_string(session->out_buffer, sub) < 0) { goto error; } ssh_string_free(usr); ssh_string_free(service); ssh_string_free(method); ssh_string_free(sub); session->auth_state=SSH_AUTH_STATE_KBDINT_SENT; if (packet_send(session) == SSH_ERROR) { leave_function(); return rc; } rc = wait_auth_status(session); leave_function(); return rc; error: buffer_reinit(session->out_buffer); ssh_string_free(usr); ssh_string_free(service); ssh_string_free(method); ssh_string_free(sub); leave_function(); return rc; } /** * @internal * @brief handles a SSH_USERAUTH_INFO_REQUEST packet, as used in * keyboard-interactive authentication, and changes the * authentication state. */ SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { ssh_string name; /* name of the "asking" window showed to client */ ssh_string instruction; ssh_string tmp; uint32_t nprompts; uint32_t i; (void)user; (void)type; enter_function(); name = buffer_get_ssh_string(packet); instruction = buffer_get_ssh_string(packet); tmp = buffer_get_ssh_string(packet); buffer_get_u32(packet, &nprompts); if (name == NULL || instruction == NULL || tmp == NULL) { ssh_string_free(name); ssh_string_free(instruction); /* tmp if empty if we got here */ ssh_set_error(session, SSH_FATAL, "Invalid USERAUTH_INFO_REQUEST msg"); leave_function(); return SSH_PACKET_USED; } ssh_string_free(tmp); if (session->kbdint == NULL) { session->kbdint = kbdint_new(); if (session->kbdint == NULL) { ssh_set_error_oom(session); ssh_string_free(name); ssh_string_free(instruction); leave_function(); return SSH_PACKET_USED; } } else { kbdint_clean(session->kbdint); } session->kbdint->name = ssh_string_to_char(name); ssh_string_free(name); if (session->kbdint->name == NULL) { ssh_set_error_oom(session); kbdint_free(session->kbdint); leave_function(); return SSH_PACKET_USED; } session->kbdint->instruction = ssh_string_to_char(instruction); ssh_string_free(instruction); if (session->kbdint->instruction == NULL) { ssh_set_error_oom(session); kbdint_free(session->kbdint); session->kbdint = NULL; leave_function(); return SSH_PACKET_USED; } nprompts = ntohl(nprompts); ssh_log(session,SSH_LOG_PACKET,"kbdint: %d prompts",nprompts); if (nprompts > KBDINT_MAX_PROMPT) { ssh_set_error(session, SSH_FATAL, "Too much prompt asked from server: %u (0x%.4x)", nprompts, nprompts); kbdint_free(session->kbdint); session->kbdint = NULL; leave_function(); return SSH_PACKET_USED; } session->kbdint->nprompts = nprompts; session->kbdint->prompts = malloc(nprompts * sizeof(char *)); if (session->kbdint->prompts == NULL) { session->kbdint->nprompts = 0; ssh_set_error_oom(session); kbdint_free(session->kbdint); session->kbdint = NULL; leave_function(); return SSH_PACKET_USED; } memset(session->kbdint->prompts, 0, nprompts * sizeof(char *)); session->kbdint->echo = malloc(nprompts); if (session->kbdint->echo == NULL) { session->kbdint->nprompts = 0; ssh_set_error_oom(session); kbdint_free(session->kbdint); session->kbdint = NULL; leave_function(); return SSH_PACKET_USED; } memset(session->kbdint->echo, 0, nprompts); for (i = 0; i < nprompts; i++) { tmp = buffer_get_ssh_string(packet); buffer_get_u8(packet, &session->kbdint->echo[i]); if (tmp == NULL) { ssh_set_error(session, SSH_FATAL, "Short INFO_REQUEST packet"); kbdint_free(session->kbdint); session->kbdint = NULL; leave_function(); return SSH_PACKET_USED; } session->kbdint->prompts[i] = ssh_string_to_char(tmp); ssh_string_free(tmp); if (session->kbdint->prompts[i] == NULL) { ssh_set_error_oom(session); kbdint_free(session->kbdint); session->kbdint = NULL; leave_function(); return SSH_PACKET_USED; } } session->auth_state=SSH_AUTH_STATE_INFO; leave_function(); return SSH_PACKET_USED; } /** * @internal * @brief Sends the current challenge response and wait for a * reply from the server * @returns SSH_AUTH_INFO if more info is needed * @returns SSH_AUTH_SUCCESS * @returns SSH_AUTH_FAILURE * @returns SSH_AUTH_PARTIAL */ static int kbdauth_send(ssh_session session) { ssh_string answer = NULL; int rc = SSH_AUTH_ERROR; uint32_t i; enter_function(); if (buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_INFO_RESPONSE) < 0 || buffer_add_u32(session->out_buffer, htonl(session->kbdint->nprompts)) < 0) { goto error; } for (i = 0; i < session->kbdint->nprompts; i++) { if (session->kbdint->answers[i]) { answer = ssh_string_from_char(session->kbdint->answers[i]); } else { answer = ssh_string_from_char(""); } if (answer == NULL) { goto error; } if (buffer_add_ssh_string(session->out_buffer, answer) < 0) { goto error; } ssh_string_burn(answer); ssh_string_free(answer); } session->auth_state=SSH_AUTH_STATE_KBDINT_SENT; kbdint_free(session->kbdint); session->kbdint = NULL; if (packet_send(session) == SSH_ERROR) { leave_function(); return rc; } rc = wait_auth_status(session); leave_function(); return rc; error: buffer_reinit(session->out_buffer); ssh_string_burn(answer); ssh_string_free(answer); leave_function(); return rc; } /** * @brief Try to authenticate through the "keyboard-interactive" method. * * @param[in] session The ssh session to use. * * @param[in] user The username to authenticate. You can specify NULL if * ssh_option_set_username() has been used. You cannot try * two different logins in a row. * * @param[in] submethods Undocumented. Set it to NULL. * * @returns SSH_AUTH_ERROR: A serious error happened\n * SSH_AUTH_DENIED: Authentication failed : use another method\n * SSH_AUTH_PARTIAL: You've been partially authenticated, you still * have to use another method\n * SSH_AUTH_SUCCESS: Authentication success\n * SSH_AUTH_INFO: The server asked some questions. Use * ssh_userauth_kbdint_getnprompts() and such. * * @see ssh_userauth_kbdint_getnprompts() * @see ssh_userauth_kbdint_getname() * @see ssh_userauth_kbdint_getinstruction() * @see ssh_userauth_kbdint_getprompt() * @see ssh_userauth_kbdint_setanswer() */ int ssh_userauth_kbdint(ssh_session session, const char *user, const char *submethods) { int rc = SSH_AUTH_ERROR; if (session->version == 1) { /* No keyb-interactive for ssh1 */ return SSH_AUTH_DENIED; } enter_function(); if (session->kbdint == NULL) { /* first time we call. we must ask for a challenge */ if (user == NULL) { if ((user = session->username) == NULL) { if (ssh_options_apply(session) < 0) { leave_function(); return SSH_AUTH_ERROR; } else { user = session->username; } } } if (ask_userauth(session)) { leave_function(); return SSH_AUTH_ERROR; } rc = kbdauth_init(session, user, submethods); leave_function(); return rc; } /* * If we are at this point, it is because session->kbdint exists. * It means the user has set some information there we need to send * the server and then we need to ack the status (new questions or ok * pass in). */ rc = kbdauth_send(session); leave_function(); return rc; } /** * @brief Get the number of prompts (questions) the server has given. * * You have called ssh_userauth_kbdint() and got SSH_AUTH_INFO. This * function returns the questions from the server. * * @param[in] session The ssh session to use. * * @returns The number of prompts. */ int ssh_userauth_kbdint_getnprompts(ssh_session session) { return session->kbdint->nprompts; } /** * @brief Get the "name" of the message block. * * You have called ssh_userauth_kbdint() and got SSH_AUTH_INFO. This * function returns the questions from the server. * * @param[in] session The ssh session to use. * * @returns The name of the message block. Do not free it. */ const char *ssh_userauth_kbdint_getname(ssh_session session) { return session->kbdint->name; } /** * @brief Get the "instruction" of the message block. * * You have called ssh_userauth_kbdint() and got SSH_AUTH_INFO. This * function returns the questions from the server. * * @param[in] session The ssh session to use. * * @returns The instruction of the message block. */ const char *ssh_userauth_kbdint_getinstruction(ssh_session session) { return session->kbdint->instruction; } /** * @brief Get a prompt from a message block. * * You have called ssh_userauth_kbdint() and got SSH_AUTH_INFO. This * function returns the questions from the server. * * @param[in] session The ssh session to use. * * @param[in] i The index number of the i'th prompt. * * @param[in] echo When different of NULL, it will obtain a boolean meaning * that the resulting user input should be echoed or not * (like passwords). * * @returns A pointer to the prompt. Do not free it. */ const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo) { if (i > session->kbdint->nprompts) { return NULL; } if (echo) { *echo = session->kbdint->echo[i]; } return session->kbdint->prompts[i]; } /** * @brief Set the answer for a question from a message block. * * If you have called ssh_userauth_kbdint() and got SSH_AUTH_INFO, this * function returns the questions from the server. * * @param[in] session The ssh session to use. * * @param[in] i index The number of the ith prompt. * * @param[in] answer The answer to give to the server. * * @return 0 on success, < 0 on error. */ int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, const char *answer) { if (session == NULL || answer == NULL || i > session->kbdint->nprompts) { return -1; } if (session->kbdint->answers == NULL) { session->kbdint->answers = malloc(sizeof(char*) * session->kbdint->nprompts); if (session->kbdint->answers == NULL) { return -1; } memset(session->kbdint->answers, 0, sizeof(char *) * session->kbdint->nprompts); } if (session->kbdint->answers[i]) { BURN_STRING(session->kbdint->answers[i]); SAFE_FREE(session->kbdint->answers[i]); } session->kbdint->answers[i] = strdup(answer); if (session->kbdint->answers[i] == NULL) { return -1; } return 0; } /* @} */ /* vim: set ts=4 sw=4 et cindent: */