#include "config.h"
#include <stdio.h>
#include <string.h>
#ifndef _WIN32
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include "libssh/priv.h"
#include "libssh/crypto.h"
#include "libssh/ssh2.h"
#include "libssh/buffer.h"
#include "libssh/agent.h"
#include "libssh/misc.h"
#include "libssh/packet.h"
#include "libssh/session.h"
#include "libssh/keys.h"
#include "libssh/auth.h"
#include "libssh/pki.h"
#include "libssh/gssapi.h"
#include "libssh/legacy.h"
static int ssh_userauth_request_service(ssh_session session) {
int rc;
rc = ssh_service_request(session, "ssh-userauth");
if ((rc != SSH_OK) && (rc != SSH_AGAIN)) {
SSH_LOG(SSH_LOG_WARN,
"Failed to request \"ssh-userauth\" service");
}
return rc;
}
static int ssh_auth_response_termination(void *user) {
ssh_session session = (ssh_session)user;
switch (session->auth.state) {
case SSH_AUTH_STATE_NONE:
case SSH_AUTH_STATE_KBDINT_SENT:
case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT:
case SSH_AUTH_STATE_GSSAPI_TOKEN:
case SSH_AUTH_STATE_GSSAPI_MIC_SENT:
case SSH_AUTH_STATE_PUBKEY_AUTH_SENT:
case SSH_AUTH_STATE_PUBKEY_OFFER_SENT:
case SSH_AUTH_STATE_PASSWORD_AUTH_SENT:
case SSH_AUTH_STATE_AUTH_NONE_SENT:
return 0;
default:
return 1;
}
}
static const char *ssh_auth_get_current_method(ssh_session session)
{
const char *method = "unknown";
switch (session->auth.current_method) {
case SSH_AUTH_METHOD_NONE:
method = "none";
break;
case SSH_AUTH_METHOD_PASSWORD:
method = "password";
break;
case SSH_AUTH_METHOD_PUBLICKEY:
method = "publickey";
break;
case SSH_AUTH_METHOD_HOSTBASED:
method = "hostbased";
break;
case SSH_AUTH_METHOD_INTERACTIVE:
method = "keyboard interactive";
break;
case SSH_AUTH_METHOD_GSSAPI_MIC:
method = "gssapi";
break;
default:
break;
}
return method;
}
static int ssh_userauth_get_response(ssh_session session) {
int rc = SSH_AUTH_ERROR;
rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER,
ssh_auth_response_termination, session);
if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
if (!ssh_auth_response_termination(session)) {
return SSH_AUTH_AGAIN;
}
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_GSSAPI_REQUEST_SENT:
case SSH_AUTH_STATE_GSSAPI_TOKEN:
case SSH_AUTH_STATE_GSSAPI_MIC_SENT:
case SSH_AUTH_STATE_PUBKEY_OFFER_SENT:
case SSH_AUTH_STATE_PUBKEY_AUTH_SENT:
case SSH_AUTH_STATE_PASSWORD_AUTH_SENT:
case SSH_AUTH_STATE_AUTH_NONE_SENT:
case SSH_AUTH_STATE_NONE:
rc = SSH_AUTH_ERROR;
break;
}
return rc;
}
SSH_PACKET_CALLBACK(ssh_packet_userauth_banner) {
ssh_string banner;
(void)type;
(void)user;
banner = ssh_buffer_get_ssh_string(packet);
if (banner == NULL) {
SSH_LOG(SSH_LOG_WARN,
"Invalid SSH_USERAUTH_BANNER packet");
} else {
SSH_LOG(SSH_LOG_DEBUG,
"Received SSH_USERAUTH_BANNER packet");
if (session->banner != NULL)
SSH_STRING_FREE(session->banner);
session->banner = banner;
}
return SSH_PACKET_USED;
}
SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) {
const char *current_method = ssh_auth_get_current_method(session);
char *auth_methods = NULL;
uint8_t partial = 0;
int rc;
(void) type;
(void) user;
rc = ssh_buffer_unpack(packet, "sb", &auth_methods, &partial);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL,
"Invalid SSH_MSG_USERAUTH_FAILURE message");
session->auth.state = SSH_AUTH_STATE_ERROR;
goto end;
}
if (partial) {
session->auth.state = SSH_AUTH_STATE_PARTIAL;
SSH_LOG(SSH_LOG_INFO,
"Partial success for '%s'. Authentication that can continue: %s",
current_method,
auth_methods);
} else {
session->auth.state = SSH_AUTH_STATE_FAILED;
ssh_set_error(session, SSH_REQUEST_DENIED,
"Access denied for '%s'. Authentication that can continue: %s",
current_method,
auth_methods);
SSH_LOG(SSH_LOG_INFO,
"%s",
ssh_get_error(session));
}
session->auth.supported_methods = 0;
if (strstr(auth_methods, "password") != NULL) {
session->auth.supported_methods |= SSH_AUTH_METHOD_PASSWORD;
}
if (strstr(auth_methods, "keyboard-interactive") != NULL) {
session->auth.supported_methods |= SSH_AUTH_METHOD_INTERACTIVE;
}
if (strstr(auth_methods, "publickey") != NULL) {
session->auth.supported_methods |= SSH_AUTH_METHOD_PUBLICKEY;
}
if (strstr(auth_methods, "hostbased") != NULL) {
session->auth.supported_methods |= SSH_AUTH_METHOD_HOSTBASED;
}
if (strstr(auth_methods, "gssapi-with-mic") != NULL) {
session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_MIC;
}
end:
session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN;
SAFE_FREE(auth_methods);
return SSH_PACKET_USED;
}
SSH_PACKET_CALLBACK(ssh_packet_userauth_success)
{
struct ssh_crypto_struct *crypto = NULL;
(void)packet;
(void)type;
(void)user;
SSH_LOG(SSH_LOG_DEBUG, "Authentication successful");
SSH_LOG(SSH_LOG_TRACE, "Received SSH_USERAUTH_SUCCESS");
session->auth.state = SSH_AUTH_STATE_SUCCESS;
session->session_state = SSH_SESSION_STATE_AUTHENTICATED;
session->flags |= SSH_SESSION_FLAG_AUTHENTICATED;
crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT);
if (crypto != NULL && crypto->delayed_compress_out) {
SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression OUT");
crypto->do_compress_out = 1;
}
crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN);
if (crypto != NULL && crypto->delayed_compress_in) {
SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression IN");
crypto->do_compress_in = 1;
}
ssh_reset_error(session);
session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN;
return SSH_PACKET_USED;
}
SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok) {
int rc;
SSH_LOG(SSH_LOG_TRACE,
"Received SSH_USERAUTH_PK_OK/INFO_REQUEST/GSSAPI_RESPONSE");
if (session->auth.state == SSH_AUTH_STATE_KBDINT_SENT) {
SSH_LOG(SSH_LOG_TRACE,
"keyboard-interactive context, "
"assuming SSH_USERAUTH_INFO_REQUEST");
rc = ssh_packet_userauth_info_request(session,type,packet,user);
#ifdef WITH_GSSAPI
} else if (session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT) {
rc = ssh_packet_userauth_gssapi_response(session, type, packet, user);
#endif
} else if (session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT) {
session->auth.state = SSH_AUTH_STATE_PK_OK;
SSH_LOG(SSH_LOG_TRACE, "Assuming SSH_USERAUTH_PK_OK");
rc = SSH_PACKET_USED;
} else {
session->auth.state = SSH_AUTH_STATE_ERROR;
SSH_LOG(SSH_LOG_TRACE, "SSH_USERAUTH_PK_OK received in wrong state");
rc = SSH_PACKET_USED;
}
return rc;
}
int ssh_userauth_list(ssh_session session, const char *username)
{
(void) username;
if (session == NULL) {
return 0;
}
return session->auth.supported_methods;
}
int ssh_userauth_none(ssh_session session, const char *username) {
int rc;
switch(session->pending_call_state) {
case SSH_PENDING_CALL_NONE:
break;
case SSH_PENDING_CALL_AUTH_NONE:
goto pending;
default:
ssh_set_error(session, SSH_FATAL,
"Wrong state (%d) during pending SSH call",
session->pending_call_state);
return SSH_AUTH_ERROR;
}
rc = ssh_userauth_request_service(session);
if (rc == SSH_AGAIN) {
return SSH_AUTH_AGAIN;
} else if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
rc = ssh_buffer_pack(session->out_buffer, "bsss",
SSH2_MSG_USERAUTH_REQUEST,
username ? username : session->opts.username,
"ssh-connection",
"none"
);
if (rc < 0) {
goto fail;
}
session->auth.current_method = SSH_AUTH_METHOD_NONE;
session->auth.state = SSH_AUTH_STATE_AUTH_NONE_SENT;
session->pending_call_state = SSH_PENDING_CALL_AUTH_NONE;
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
pending:
rc = ssh_userauth_get_response(session);
if (rc != SSH_AUTH_AGAIN) {
session->pending_call_state = SSH_PENDING_CALL_NONE;
}
return rc;
fail:
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
return SSH_AUTH_ERROR;
}
int ssh_userauth_try_publickey(ssh_session session,
const char *username,
const ssh_key pubkey)
{
ssh_string pubkey_s = NULL;
const char *sig_type_c = NULL;
int rc;
if (session == NULL) {
return SSH_AUTH_ERROR;
}
if (pubkey == NULL || !ssh_key_is_public(pubkey)) {
ssh_set_error(session, SSH_FATAL, "Invalid pubkey");
return SSH_AUTH_ERROR;
}
switch(session->pending_call_state) {
case SSH_PENDING_CALL_NONE:
break;
case SSH_PENDING_CALL_AUTH_OFFER_PUBKEY:
goto pending;
default:
ssh_set_error(session,
SSH_FATAL,
"Wrong state (%d) during pending SSH call",
session->pending_call_state);
return SSH_ERROR;
}
sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type);
if (sig_type_c == NULL) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"Invalid key type (unknown)");
return SSH_AUTH_DENIED;
}
if (!ssh_key_algorithm_allowed(session, sig_type_c)) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"The key algorithm '%s' is not allowed to be used by"
" PUBLICKEY_ACCEPTED_TYPES configuration option",
sig_type_c);
return SSH_AUTH_DENIED;
}
rc = ssh_userauth_request_service(session);
if (rc == SSH_AGAIN) {
return SSH_AUTH_AGAIN;
} else if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_s);
if (rc < 0) {
goto fail;
}
rc = ssh_buffer_pack(session->out_buffer, "bsssbsS",
SSH2_MSG_USERAUTH_REQUEST,
username ? username : session->opts.username,
"ssh-connection",
"publickey",
0,
sig_type_c,
pubkey_s
);
if (rc < 0) {
goto fail;
}
SSH_STRING_FREE(pubkey_s);
session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY;
session->auth.state = SSH_AUTH_STATE_PUBKEY_OFFER_SENT;
session->pending_call_state = SSH_PENDING_CALL_AUTH_OFFER_PUBKEY;
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
pending:
rc = ssh_userauth_get_response(session);
if (rc != SSH_AUTH_AGAIN) {
session->pending_call_state = SSH_PENDING_CALL_NONE;
}
return rc;
fail:
SSH_STRING_FREE(pubkey_s);
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
return SSH_AUTH_ERROR;
}
int ssh_userauth_publickey(ssh_session session,
const char *username,
const ssh_key privkey)
{
ssh_string str = NULL;
int rc;
const char *sig_type_c = NULL;
enum ssh_keytypes_e key_type;
enum ssh_digest_e hash_type;
if (session == NULL) {
return SSH_AUTH_ERROR;
}
if (privkey == NULL || !ssh_key_is_private(privkey)) {
ssh_set_error(session, SSH_FATAL, "Invalid private key");
return SSH_AUTH_ERROR;
}
switch(session->pending_call_state) {
case SSH_PENDING_CALL_NONE:
break;
case SSH_PENDING_CALL_AUTH_PUBKEY:
goto pending;
default:
ssh_set_error(session,
SSH_FATAL,
"Bad call during pending SSH call in ssh_userauth_try_publickey");
return SSH_AUTH_ERROR;
}
key_type = privkey->cert != NULL ? privkey->cert_type : privkey->type;
sig_type_c = ssh_key_get_signature_algorithm(session, key_type);
if (sig_type_c == NULL) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"Invalid key type (unknown)");
return SSH_AUTH_DENIED;
}
if (!ssh_key_algorithm_allowed(session, sig_type_c)) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"The key algorithm '%s' is not allowed to be used by"
" PUBLICKEY_ACCEPTED_TYPES configuration option",
sig_type_c);
return SSH_AUTH_DENIED;
}
rc = ssh_userauth_request_service(session);
if (rc == SSH_AGAIN) {
return SSH_AUTH_AGAIN;
} else if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
rc = ssh_pki_export_pubkey_blob(privkey, &str);
if (rc < 0) {
goto fail;
}
rc = ssh_buffer_pack(session->out_buffer, "bsssbsS",
SSH2_MSG_USERAUTH_REQUEST,
username ? username : session->opts.username,
"ssh-connection",
"publickey",
1,
sig_type_c,
str
);
if (rc < 0) {
goto fail;
}
SSH_STRING_FREE(str);
hash_type = ssh_key_type_to_hash(session, privkey->type);
str = ssh_pki_do_sign(session, session->out_buffer, privkey, hash_type);
if (str == NULL) {
goto fail;
}
rc = ssh_buffer_add_ssh_string(session->out_buffer, str);
SSH_STRING_FREE(str);
str = NULL;
if (rc < 0) {
goto fail;
}
session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY;
session->auth.state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT;
session->pending_call_state = SSH_PENDING_CALL_AUTH_PUBKEY;
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
pending:
rc = ssh_userauth_get_response(session);
if (rc != SSH_AUTH_AGAIN) {
session->pending_call_state = SSH_PENDING_CALL_NONE;
}
return rc;
fail:
SSH_STRING_FREE(str);
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
return SSH_AUTH_ERROR;
}
static int ssh_userauth_agent_publickey(ssh_session session,
const char *username,
ssh_key pubkey)
{
ssh_string pubkey_s = NULL;
ssh_string sig_blob = NULL;
const char *sig_type_c = NULL;
int rc;
switch(session->pending_call_state) {
case SSH_PENDING_CALL_NONE:
break;
case SSH_PENDING_CALL_AUTH_AGENT:
goto pending;
default:
ssh_set_error(session,
SSH_FATAL,
"Bad call during pending SSH call in ssh_userauth_try_publickey");
return SSH_ERROR;
}
rc = ssh_userauth_request_service(session);
if (rc == SSH_AGAIN) {
return SSH_AUTH_AGAIN;
} else if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_s);
if (rc < 0) {
goto fail;
}
sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type);
if (sig_type_c == NULL) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"Invalid key type (unknown)");
SSH_STRING_FREE(pubkey_s);
return SSH_AUTH_DENIED;
}
if (!ssh_key_algorithm_allowed(session, sig_type_c)) {
ssh_set_error(session, SSH_REQUEST_DENIED,
"The key algorithm '%s' is not allowed to be used by"
" PUBLICKEY_ACCEPTED_TYPES configuration option",
sig_type_c);
SSH_STRING_FREE(pubkey_s);
return SSH_AUTH_DENIED;
}
rc = ssh_buffer_pack(session->out_buffer, "bsssbsS",
SSH2_MSG_USERAUTH_REQUEST,
username ? username : session->opts.username,
"ssh-connection",
"publickey",
1,
sig_type_c,
pubkey_s
);
SSH_STRING_FREE(pubkey_s);
if (rc < 0) {
goto fail;
}
sig_blob = ssh_pki_do_sign_agent(session, session->out_buffer, pubkey);
if (sig_blob == NULL) {
goto fail;
}
rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob);
SSH_STRING_FREE(sig_blob);
if (rc < 0) {
goto fail;
}
session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY;
session->auth.state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT;
session->pending_call_state = SSH_PENDING_CALL_AUTH_AGENT;
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
pending:
rc = ssh_userauth_get_response(session);
if (rc != SSH_AUTH_AGAIN) {
session->pending_call_state = SSH_PENDING_CALL_NONE;
}
return rc;
fail:
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
SSH_STRING_FREE(pubkey_s);
return SSH_AUTH_ERROR;
}
enum ssh_agent_state_e {
SSH_AGENT_STATE_NONE = 0,
SSH_AGENT_STATE_PUBKEY,
SSH_AGENT_STATE_AUTH
};
struct ssh_agent_state_struct {
enum ssh_agent_state_e state;
ssh_key pubkey;
char *comment;
};
void ssh_agent_state_free(void *data) {
struct ssh_agent_state_struct *state = data;
if (state) {
SSH_STRING_FREE_CHAR(state->comment);
ssh_key_free(state->pubkey);
free (state);
}
}
int ssh_userauth_agent(ssh_session session,
const char *username) {
int rc = SSH_AUTH_ERROR;
struct ssh_agent_state_struct *state;
if (session == NULL) {
return SSH_AUTH_ERROR;
}
if (!ssh_agent_is_running(session)) {
return SSH_AUTH_DENIED;
}
if (!session->agent_state) {
session->agent_state = malloc(sizeof(struct ssh_agent_state_struct));
if (!session->agent_state) {
ssh_set_error_oom(session);
return SSH_AUTH_ERROR;
}
ZERO_STRUCTP(session->agent_state);
session->agent_state->state=SSH_AGENT_STATE_NONE;
}
state = session->agent_state;
if (state->pubkey == NULL) {
state->pubkey = ssh_agent_get_first_ident(session, &state->comment);
}
if (state->pubkey == NULL) {
return SSH_AUTH_DENIED;
}
while (state->pubkey != NULL) {
if (state->state == SSH_AGENT_STATE_NONE) {
SSH_LOG(SSH_LOG_DEBUG,
"Trying identity %s", state->comment);
}
if (state->state == SSH_AGENT_STATE_NONE ||
state->state == SSH_AGENT_STATE_PUBKEY) {
rc = ssh_userauth_try_publickey(session, username, state->pubkey);
if (rc == SSH_AUTH_ERROR) {
ssh_agent_state_free (state);
session->agent_state = NULL;
return rc;
} else if (rc == SSH_AUTH_AGAIN) {
state->state = SSH_AGENT_STATE_PUBKEY;
return rc;
} else if (rc != SSH_AUTH_SUCCESS) {
SSH_LOG(SSH_LOG_DEBUG,
"Public key of %s refused by server", state->comment);
SSH_STRING_FREE_CHAR(state->comment);
state->comment = NULL;
ssh_key_free(state->pubkey);
state->pubkey = ssh_agent_get_next_ident(session, &state->comment);
state->state = SSH_AGENT_STATE_NONE;
continue;
}
SSH_LOG(SSH_LOG_DEBUG,
"Public key of %s accepted by server", state->comment);
state->state = SSH_AGENT_STATE_AUTH;
}
if (state->state == SSH_AGENT_STATE_AUTH) {
rc = ssh_userauth_agent_publickey(session, username, state->pubkey);
if (rc == SSH_AUTH_AGAIN)
return rc;
SSH_STRING_FREE_CHAR(state->comment);
state->comment = NULL;
if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_PARTIAL) {
ssh_agent_state_free (session->agent_state);
session->agent_state = NULL;
return rc;
} else if (rc != SSH_AUTH_SUCCESS) {
SSH_LOG(SSH_LOG_INFO,
"Server accepted public key but refused the signature");
ssh_key_free(state->pubkey);
state->pubkey = ssh_agent_get_next_ident(session, &state->comment);
state->state = SSH_AGENT_STATE_NONE;
continue;
}
ssh_agent_state_free (session->agent_state);
session->agent_state = NULL;
return SSH_AUTH_SUCCESS;
}
}
ssh_agent_state_free (session->agent_state);
session->agent_state = NULL;
return rc;
}
enum ssh_auth_auto_state_e {
SSH_AUTH_AUTO_STATE_NONE = 0,
SSH_AUTH_AUTO_STATE_PUBKEY,
SSH_AUTH_AUTO_STATE_KEY_IMPORTED,
SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED
};
struct ssh_auth_auto_state_struct {
enum ssh_auth_auto_state_e state;
struct ssh_iterator *it;
ssh_key privkey;
ssh_key pubkey;
};
int ssh_userauth_publickey_auto_get_current_identity(ssh_session session,
char** value)
{
const char *id = NULL;
if (session == NULL) {
return SSH_ERROR;
}
if (value == NULL) {
ssh_set_error_invalid(session);
return SSH_ERROR;
}
if (session->auth.auto_state != NULL && session->auth.auto_state->it != NULL) {
id = session->auth.auto_state->it->data;
}
if (id == NULL) {
return SSH_ERROR;
}
*value = strdup(id);
if (*value == NULL) {
ssh_set_error_oom(session);
return SSH_ERROR;
}
return SSH_OK;
}
int ssh_userauth_publickey_auto(ssh_session session,
const char *username,
const char *passphrase)
{
ssh_auth_callback auth_fn = NULL;
void *auth_data = NULL;
struct ssh_auth_auto_state_struct *state;
int rc;
if (session == NULL) {
return SSH_AUTH_ERROR;
}
if (! (session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH)) {
session->auth.supported_methods &= ~SSH_AUTH_METHOD_PUBLICKEY;
return SSH_AUTH_DENIED;
}
if (session->common.callbacks) {
auth_fn = session->common.callbacks->auth_function;
auth_data = session->common.callbacks->userdata;
}
if (!session->auth.auto_state) {
session->auth.auto_state =
calloc(1, sizeof(struct ssh_auth_auto_state_struct));
if (!session->auth.auto_state) {
ssh_set_error_oom(session);
return SSH_AUTH_ERROR;
}
session->auth.auto_state->state = SSH_AUTH_AUTO_STATE_NONE;
}
state = session->auth.auto_state;
if (state->state == SSH_AUTH_AUTO_STATE_NONE) {
rc = ssh_userauth_agent(session, username);
if (rc == SSH_AUTH_SUCCESS ||
rc == SSH_AUTH_PARTIAL ||
rc == SSH_AUTH_AGAIN ) {
return rc;
}
state->state = SSH_AUTH_AUTO_STATE_PUBKEY;
}
if (state->it == NULL) {
state->it = ssh_list_get_iterator(session->opts.identity);
}
while (state->it != NULL) {
const char *privkey_file = state->it->data;
char pubkey_file[PATH_MAX] = {0};
if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY) {
SSH_LOG(SSH_LOG_DEBUG,
"Trying to authenticate with %s", privkey_file);
state->privkey = NULL;
state->pubkey = NULL;
if (ssh_pki_is_uri(privkey_file)) {
char *pub_uri_from_priv = NULL;
SSH_LOG(SSH_LOG_INFO,
"Authenticating with PKCS #11 URI.");
pub_uri_from_priv = ssh_pki_export_pub_uri_from_priv_uri(privkey_file);
if (pub_uri_from_priv == NULL) {
return SSH_ERROR;
} else {
snprintf(pubkey_file, sizeof(pubkey_file), "%s",
pub_uri_from_priv);
SAFE_FREE(pub_uri_from_priv);
}
} else {
snprintf(pubkey_file, sizeof(pubkey_file), "%s.pub", privkey_file);
}
rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey);
if (rc == SSH_ERROR) {
ssh_set_error(session,
SSH_FATAL,
"Failed to import public key: %s",
pubkey_file);
SAFE_FREE(session->auth.auto_state);
return SSH_AUTH_ERROR;
} else if (rc == SSH_EOF) {
rc = ssh_pki_import_privkey_file(privkey_file,
passphrase,
auth_fn,
auth_data,
&state->privkey);
if (rc == SSH_ERROR) {
ssh_set_error(session,
SSH_FATAL,
"Failed to read private key: %s",
privkey_file);
state->it=state->it->next;
continue;
} else if (rc == SSH_EOF) {
SSH_LOG(SSH_LOG_DEBUG,
"Private key %s doesn't exist.",
privkey_file);
state->it=state->it->next;
continue;
}
rc = ssh_pki_export_privkey_to_pubkey(state->privkey, &state->pubkey);
if (rc == SSH_ERROR) {
ssh_key_free(state->privkey);
SAFE_FREE(session->auth.auto_state);
return SSH_AUTH_ERROR;
}
rc = ssh_pki_export_pubkey_file(state->pubkey, pubkey_file);
if (rc == SSH_ERROR) {
SSH_LOG(SSH_LOG_WARN,
"Could not write public key to file: %s",
pubkey_file);
}
}
state->state = SSH_AUTH_AUTO_STATE_KEY_IMPORTED;
}
if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) {
rc = ssh_userauth_try_publickey(session, username, state->pubkey);
if (rc == SSH_AUTH_ERROR) {
SSH_LOG(SSH_LOG_WARN,
"Public key authentication error for %s",
privkey_file);
ssh_key_free(state->privkey);
state->privkey = NULL;
ssh_key_free(state->pubkey);
state->pubkey = NULL;
SAFE_FREE(session->auth.auto_state);
return rc;
} else if (rc == SSH_AUTH_AGAIN) {
return rc;
} else if (rc != SSH_AUTH_SUCCESS) {
SSH_LOG(SSH_LOG_DEBUG,
"Public key for %s refused by server",
privkey_file);
ssh_key_free(state->privkey);
state->privkey = NULL;
ssh_key_free(state->pubkey);
state->pubkey = NULL;
state->it=state->it->next;
state->state = SSH_AUTH_AUTO_STATE_PUBKEY;
continue;
}
state->state = SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED;
}
if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED) {
if (state->privkey == NULL) {
rc = ssh_pki_import_privkey_file(privkey_file,
passphrase,
auth_fn,
auth_data,
&state->privkey);
if (rc == SSH_ERROR) {
ssh_key_free(state->pubkey);
state->pubkey=NULL;
ssh_set_error(session,
SSH_FATAL,
"Failed to read private key: %s",
privkey_file);
state->it=state->it->next;
state->state = SSH_AUTH_AUTO_STATE_PUBKEY;
continue;
} else if (rc == SSH_EOF) {
ssh_key_free(state->pubkey);
state->pubkey = NULL;
SSH_LOG(SSH_LOG_INFO,
"Private key %s doesn't exist.",
privkey_file);
state->it = state->it->next;
state->state = SSH_AUTH_AUTO_STATE_PUBKEY;
continue;
}
}
rc = ssh_userauth_publickey(session, username, state->privkey);
if (rc != SSH_AUTH_AGAIN && rc != SSH_AUTH_DENIED) {
ssh_key_free(state->privkey);
ssh_key_free(state->pubkey);
SAFE_FREE(session->auth.auto_state);
if (rc == SSH_AUTH_SUCCESS) {
SSH_LOG(SSH_LOG_INFO,
"Successfully authenticated using %s",
privkey_file);
}
return rc;
}
if (rc == SSH_AUTH_AGAIN) {
return rc;
}
ssh_key_free(state->privkey);
ssh_key_free(state->pubkey);
SSH_LOG(SSH_LOG_WARN,
"The server accepted the public key but refused the signature");
state->it = state->it->next;
state->state = SSH_AUTH_AUTO_STATE_PUBKEY;
}
}
SSH_LOG(SSH_LOG_INFO,
"Tried every public key, none matched");
SAFE_FREE(session->auth.auto_state);
return SSH_AUTH_DENIED;
}
int ssh_userauth_password(ssh_session session,
const char *username,
const char *password) {
int rc;
switch(session->pending_call_state) {
case SSH_PENDING_CALL_NONE:
break;
case SSH_PENDING_CALL_AUTH_PASSWORD:
goto pending;
default:
ssh_set_error(session,
SSH_FATAL,
"Wrong state (%d) during pending SSH call",
session->pending_call_state);
return SSH_ERROR;
}
rc = ssh_userauth_request_service(session);
if (rc == SSH_AGAIN) {
return SSH_AUTH_AGAIN;
} else if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
rc = ssh_buffer_pack(session->out_buffer, "bsssbs",
SSH2_MSG_USERAUTH_REQUEST,
username ? username : session->opts.username,
"ssh-connection",
"password",
0,
password
);
if (rc < 0) {
goto fail;
}
ssh_buffer_set_secure(session->out_buffer);
session->auth.current_method = SSH_AUTH_METHOD_PASSWORD;
session->auth.state = SSH_AUTH_STATE_PASSWORD_AUTH_SENT;
session->pending_call_state = SSH_PENDING_CALL_AUTH_PASSWORD;
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
pending:
rc = ssh_userauth_get_response(session);
if (rc != SSH_AUTH_AGAIN) {
session->pending_call_state = SSH_PENDING_CALL_NONE;
}
return rc;
fail:
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
return SSH_AUTH_ERROR;
}
int ssh_userauth_agent_pubkey(ssh_session session,
const char *username,
ssh_public_key publickey)
{
ssh_key key;
int rc;
key = ssh_key_new();
if (key == NULL) {
return SSH_AUTH_ERROR;
}
key->type = publickey->type;
key->type_c = ssh_key_type_to_char(key->type);
key->flags = SSH_KEY_FLAG_PUBLIC;
key->dsa = publickey->dsa_pub;
key->rsa = publickey->rsa_pub;
rc = ssh_userauth_agent_publickey(session, username, key);
key->dsa = NULL;
key->rsa = NULL;
ssh_key_free(key);
return rc;
}
ssh_kbdint ssh_kbdint_new(void) {
ssh_kbdint kbd;
kbd = calloc(1, sizeof(struct ssh_kbdint_struct));
if (kbd == NULL) {
return NULL;
}
return kbd;
}
void ssh_kbdint_free(ssh_kbdint kbd) {
size_t i, n;
if (kbd == NULL) {
return;
}
SAFE_FREE(kbd->name);
SAFE_FREE(kbd->instruction);
SAFE_FREE(kbd->echo);
n = kbd->nprompts;
if (kbd->prompts) {
for (i = 0; i < n; i++) {
if (kbd->prompts[i] != NULL) {
explicit_bzero(kbd->prompts[i], strlen(kbd->prompts[i]));
}
SAFE_FREE(kbd->prompts[i]);
}
SAFE_FREE(kbd->prompts);
}
n = kbd->nanswers;
if (kbd->answers) {
for (i = 0; i < n; i++) {
if (kbd->answers[i] != NULL) {
explicit_bzero(kbd->answers[i], strlen(kbd->answers[i]));
}
SAFE_FREE(kbd->answers[i]);
}
SAFE_FREE(kbd->answers);
}
SAFE_FREE(kbd);
}
void ssh_kbdint_clean(ssh_kbdint kbd) {
size_t i, n;
if (kbd == NULL) {
return;
}
SAFE_FREE(kbd->name);
SAFE_FREE(kbd->instruction);
SAFE_FREE(kbd->echo);
n = kbd->nprompts;
if (kbd->prompts) {
for (i = 0; i < n; i++) {
explicit_bzero(kbd->prompts[i], strlen(kbd->prompts[i]));
SAFE_FREE(kbd->prompts[i]);
}
SAFE_FREE(kbd->prompts);
}
n = kbd->nanswers;
if (kbd->answers) {
for (i = 0; i < n; i++) {
explicit_bzero(kbd->answers[i], strlen(kbd->answers[i]));
SAFE_FREE(kbd->answers[i]);
}
SAFE_FREE(kbd->answers);
}
kbd->nprompts = 0;
kbd->nanswers = 0;
}
static int ssh_userauth_kbdint_init(ssh_session session,
const char *username,
const char *submethods)
{
int rc;
if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT) {
goto pending;
}
if (session->pending_call_state != SSH_PENDING_CALL_NONE) {
ssh_set_error_invalid(session);
return SSH_ERROR;
}
rc = ssh_userauth_request_service(session);
if (rc == SSH_AGAIN) {
return SSH_AUTH_AGAIN;
}
if (rc != SSH_OK) {
return SSH_AUTH_ERROR;
}
rc = ssh_buffer_pack(session->out_buffer, "bsssss",
SSH2_MSG_USERAUTH_REQUEST,
username ? username : session->opts.username,
"ssh-connection",
"keyboard-interactive",
"",
submethods ? submethods : ""
);
if (rc < 0) {
goto fail;
}
session->auth.state = SSH_AUTH_STATE_KBDINT_SENT;
session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_INIT;
SSH_LOG(SSH_LOG_DEBUG,
"Sending keyboard-interactive init request");
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
pending:
rc = ssh_userauth_get_response(session);
if (rc != SSH_AUTH_AGAIN)
session->pending_call_state = SSH_PENDING_CALL_NONE;
return rc;
fail:
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
return SSH_AUTH_ERROR;
}
static int ssh_userauth_kbdint_send(ssh_session session)
{
uint32_t i;
int rc;
if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND)
goto pending;
if (session->pending_call_state != SSH_PENDING_CALL_NONE) {
ssh_set_error_invalid(session);
return SSH_ERROR;
}
rc = ssh_buffer_pack(session->out_buffer, "bd",
SSH2_MSG_USERAUTH_INFO_RESPONSE,
session->kbdint->nprompts);
if (rc < 0) {
goto fail;
}
for (i = 0; i < session->kbdint->nprompts; i++) {
rc = ssh_buffer_pack(session->out_buffer, "s",
session->kbdint->answers && session->kbdint->answers[i] ?
session->kbdint->answers[i]:"");
if (rc < 0) {
goto fail;
}
}
session->auth.current_method = SSH_AUTH_METHOD_INTERACTIVE;
session->auth.state = SSH_AUTH_STATE_KBDINT_SENT;
session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_SEND;
ssh_kbdint_free(session->kbdint);
session->kbdint = NULL;
SSH_LOG(SSH_LOG_DEBUG,
"Sending keyboard-interactive response packet");
rc = ssh_packet_send(session);
if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
pending:
rc = ssh_userauth_get_response(session);
if (rc != SSH_AUTH_AGAIN)
session->pending_call_state = SSH_PENDING_CALL_NONE;
return rc;
fail:
ssh_set_error_oom(session);
ssh_buffer_reinit(session->out_buffer);
return SSH_AUTH_ERROR;
}
SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) {
ssh_string tmp = NULL;
uint32_t nprompts;
uint32_t i;
int rc;
(void)user;
(void)type;
if (session->kbdint == NULL) {
session->kbdint = ssh_kbdint_new();
if (session->kbdint == NULL) {
ssh_set_error_oom(session);
return SSH_PACKET_USED;
}
} else {
ssh_kbdint_clean(session->kbdint);
}
rc = ssh_buffer_unpack(packet, "ssSd",
&session->kbdint->name,
&session->kbdint->instruction,
&tmp,
&nprompts
);
SSH_STRING_FREE(tmp);
if (rc != SSH_OK) {
ssh_set_error(session, SSH_FATAL, "Invalid USERAUTH_INFO_REQUEST msg");
ssh_kbdint_free(session->kbdint);
session->kbdint = NULL;
return SSH_PACKET_USED;
}
SSH_LOG(SSH_LOG_DEBUG,
"%d keyboard-interactive prompts", nprompts);
if (nprompts > KBDINT_MAX_PROMPT) {
ssh_set_error(session, SSH_FATAL,
"Too much prompts requested by the server: %u (0x%.4x)",
nprompts, nprompts);
ssh_kbdint_free(session->kbdint);
session->kbdint = NULL;
return SSH_PACKET_USED;
}
session->kbdint->nprompts = nprompts;
session->kbdint->nanswers = nprompts;
session->kbdint->prompts = calloc(nprompts, sizeof(char *));
if (session->kbdint->prompts == NULL) {
session->kbdint->nprompts = 0;
ssh_set_error_oom(session);
ssh_kbdint_free(session->kbdint);
session->kbdint = NULL;
return SSH_PACKET_USED;
}
session->kbdint->echo = calloc(nprompts, sizeof(unsigned char));
if (session->kbdint->echo == NULL) {
session->kbdint->nprompts = 0;
ssh_set_error_oom(session);
ssh_kbdint_free(session->kbdint);
session->kbdint = NULL;
return SSH_PACKET_USED;
}
for (i = 0; i < nprompts; i++) {
rc = ssh_buffer_unpack(packet, "sb",
&session->kbdint->prompts[i],
&session->kbdint->echo[i]);
if (rc == SSH_ERROR) {
ssh_set_error(session, SSH_FATAL, "Short INFO_REQUEST packet");
ssh_kbdint_free(session->kbdint);
session->kbdint = NULL;
return SSH_PACKET_USED;
}
}
session->auth.state=SSH_AUTH_STATE_INFO;
return SSH_PACKET_USED;
}
int ssh_userauth_kbdint(ssh_session session, const char *user,
const char *submethods) {
int rc = SSH_AUTH_ERROR;
if (session == NULL) {
return SSH_AUTH_ERROR;
}
if ((session->pending_call_state == SSH_PENDING_CALL_NONE && session->kbdint == NULL) ||
session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT)
rc = ssh_userauth_kbdint_init(session, user, submethods);
else if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND ||
session->kbdint != NULL) {
rc = ssh_userauth_kbdint_send(session);
} else {
rc = SSH_AUTH_ERROR;
ssh_set_error(session, SSH_FATAL, "Invalid state in %s", __func__);
}
return rc;
}
int ssh_userauth_kbdint_getnprompts(ssh_session session) {
if (session == NULL) {
return SSH_ERROR;
}
if (session->kbdint == NULL) {
ssh_set_error_invalid(session);
return SSH_ERROR;
}
return session->kbdint->nprompts;
}
const char *ssh_userauth_kbdint_getname(ssh_session session) {
if (session == NULL) {
return NULL;
}
if (session->kbdint == NULL) {
ssh_set_error_invalid(session);
return NULL;
}
return session->kbdint->name;
}
const char *ssh_userauth_kbdint_getinstruction(ssh_session session) {
if (session == NULL)
return NULL;
if (session->kbdint == NULL) {
ssh_set_error_invalid(session);
return NULL;
}
return session->kbdint->instruction;
}
const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i,
char *echo) {
if (session == NULL)
return NULL;
if (session->kbdint == NULL) {
ssh_set_error_invalid(session);
return NULL;
}
if (i > session->kbdint->nprompts) {
ssh_set_error_invalid(session);
return NULL;
}
if (echo) {
*echo = (char)session->kbdint->echo[i];
}
return session->kbdint->prompts[i];
}
#ifdef WITH_SERVER
int ssh_userauth_kbdint_getnanswers(ssh_session session) {
if (session == NULL || session->kbdint == NULL) {
return SSH_ERROR;
}
return session->kbdint->nanswers;
}
const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) {
if (session == NULL || session->kbdint == NULL
|| session->kbdint->answers == NULL) {
return NULL;
}
if (i >= session->kbdint->nanswers) {
return NULL;
}
return session->kbdint->answers[i];
}
#endif
int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i,
const char *answer) {
if (session == NULL) {
return -1;
}
if (answer == NULL || session->kbdint == NULL ||
i >= session->kbdint->nprompts) {
ssh_set_error_invalid(session);
return -1;
}
if (session->kbdint->answers == NULL) {
session->kbdint->answers = calloc(session->kbdint->nprompts, sizeof(char *));
if (session->kbdint->answers == NULL) {
ssh_set_error_oom(session);
return -1;
}
}
if (session->kbdint->answers[i]) {
explicit_bzero(session->kbdint->answers[i],
strlen(session->kbdint->answers[i]));
SAFE_FREE(session->kbdint->answers[i]);
}
session->kbdint->answers[i] = strdup(answer);
if (session->kbdint->answers[i] == NULL) {
ssh_set_error_oom(session);
return -1;
}
return 0;
}
int ssh_userauth_gssapi(ssh_session session) {
int rc = SSH_AUTH_DENIED;
#ifdef WITH_GSSAPI
switch(session->pending_call_state) {
case SSH_PENDING_CALL_NONE:
break;
case SSH_PENDING_CALL_AUTH_GSSAPI_MIC:
goto pending;
default:
ssh_set_error(session,
SSH_FATAL,
"Wrong state (%d) during pending SSH call",
session->pending_call_state);
return SSH_ERROR;
}
rc = ssh_userauth_request_service(session);
if (rc == SSH_AGAIN) {
return SSH_AUTH_AGAIN;
} else if (rc == SSH_ERROR) {
return SSH_AUTH_ERROR;
}
SSH_LOG(SSH_LOG_PROTOCOL, "Authenticating with gssapi-with-mic");
session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_MIC;
session->auth.state = SSH_AUTH_STATE_NONE;
session->pending_call_state = SSH_PENDING_CALL_AUTH_GSSAPI_MIC;
rc = ssh_gssapi_auth_mic(session);
if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_DENIED) {
session->auth.state = SSH_AUTH_STATE_NONE;
session->pending_call_state = SSH_PENDING_CALL_NONE;
return rc;
}
pending:
rc = ssh_userauth_get_response(session);
if (rc != SSH_AUTH_AGAIN) {
session->pending_call_state = SSH_PENDING_CALL_NONE;
}
#else
(void) session;
#endif
return rc;
}