diff options
author | Andreas Schneider <asn@cynapses.org> | 2010-09-06 14:28:38 +0200 |
---|---|---|
committer | Andreas Schneider <asn@cynapses.org> | 2010-09-06 14:28:38 +0200 |
commit | f7842e3a4b9acea2126ff725f993c299aef0e6db (patch) | |
tree | 18239f819a5edbcfc7f2961c48f3f9297314ef22 /src/auth.c | |
parent | 38421403d2dc45636e597f2a909daa6ae31976de (diff) | |
download | libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.gz libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.tar.xz libssh-f7842e3a4b9acea2126ff725f993c299aef0e6db.zip |
misc: Rename libssh/ to src/
Diffstat (limited to 'src/auth.c')
-rw-r--r-- | src/auth.c | 1682 |
1 files changed, 1682 insertions, 0 deletions
diff --git a/src/auth.c b/src/auth.c new file mode 100644 index 00000000..f0443db0 --- /dev/null +++ b/src/auth.c @@ -0,0 +1,1682 @@ +/* + * 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 <mail@cynapses.org> + * + * 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 <stdlib.h> +#include <stdio.h> +#include <string.h> + +#ifndef _WIN32 +#include <arpa/inet.h> +#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 + * @deprecated + * @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: */ |