Files
mscp/src/ssh.c
Ryo Nakamura ef8994d21e cache auth_bit_mask after the first none auth attempt
This fixes #36, password auth does not occur.
2025-11-08 18:50:44 +09:00

418 lines
10 KiB
C

/* SPDX-License-Identifier: GPL-3.0-only */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <ssh.h>
#include <mscp.h>
#include <strerrno.h>
#include "libssh/callbacks.h"
#include "libssh/options.h"
static int ssh_verify_known_hosts(ssh_session session);
static int ssh_authenticate_kbdint(ssh_session session);
static int ssh_set_opts(ssh_session ssh, struct mscp_ssh_opts *opts)
{
ssh_set_log_level(opts->debug_level);
if (opts->login_name &&
ssh_options_set(ssh, SSH_OPTIONS_USER, opts->login_name) < 0) {
priv_set_errv("failed to set login name");
return -1;
}
if (opts->port && ssh_options_set(ssh, SSH_OPTIONS_PORT_STR, opts->port) < 0) {
priv_set_errv("failed to set port number");
return -1;
}
if (opts->ai_family &&
ssh_options_set(ssh, SSH_OPTIONS_AI_FAMILY, &opts->ai_family) < 0) {
priv_set_errv("failed to set address family");
return -1;
}
if (opts->identity &&
ssh_options_set(ssh, SSH_OPTIONS_IDENTITY, opts->identity) < 0) {
priv_set_errv("failed to set identity");
return -1;
}
if (opts->cipher) {
if (ssh_options_set(ssh, SSH_OPTIONS_CIPHERS_C_S, opts->cipher) < 0) {
priv_set_errv("failed to set cipher for client to server");
return -1;
}
if (ssh_options_set(ssh, SSH_OPTIONS_CIPHERS_S_C, opts->cipher) < 0) {
priv_set_errv("failed to set cipher for server to client");
return -1;
}
}
if (opts->hmac) {
if (ssh_options_set(ssh, SSH_OPTIONS_HMAC_C_S, opts->hmac) < 0) {
priv_set_errv("failed to set hmac for client to server");
return -1;
}
if (ssh_options_set(ssh, SSH_OPTIONS_HMAC_S_C, opts->hmac) < 0) {
priv_set_errv("failed to set hmac for server to client");
return -1;
}
}
if (opts->compress &&
ssh_options_set(ssh, SSH_OPTIONS_COMPRESSION, opts->compress) < 0) {
priv_set_errv("failed to enable ssh compression");
return -1;
}
if (opts->ccalgo && ssh_options_set(ssh, SSH_OPTIONS_CCALGO, opts->ccalgo) < 0) {
priv_set_errv("failed to set cclago");
return -1;
}
/* if NOT specified to enable Nagle's algorithm, disable it (set TCP_NODELAY) */
if (!opts->enable_nagle) {
int v = 1;
if (ssh_options_set(ssh, SSH_OPTIONS_NODELAY, &v) < 0) {
priv_set_errv("failed to set TCP_NODELAY");
return -1;
}
}
if (opts->config && ssh_options_parse_config(ssh, opts->config) < 0) {
priv_set_errv("failed to parse ssh_config: %s", opts->config);
return -1;
}
if (opts->proxyjump) {
char buf[256];
memset(buf, 0, sizeof(buf));
snprintf(buf, sizeof(buf), "proxyjump=%s", opts->proxyjump);
if (ssh_config_parse_string(ssh, buf) != SSH_OK) {
priv_set_errv("failed to set ssh option: %s", buf);
return -1;
}
}
if (opts->options) {
int n;
for (n = 0; opts->options[n]; n++) {
if (ssh_config_parse_string(ssh, opts->options[n]) != SSH_OK) {
priv_set_errv("failed to set ssh option: %s",
opts->options[n]);
return -1;
}
}
}
return 0;
}
static int ssh_authenticate(ssh_session ssh, struct mscp_ssh_opts *opts)
{
static int auth_bit_mask;
int ret;
if (auth_bit_mask == 0) {
/* the first authentication attempt. try none auth to
* get available auth methods. */
if (ssh_userauth_none(ssh, NULL) == SSH_AUTH_SUCCESS)
return 0;
/* save auth_bit_mask for further authentications */
auth_bit_mask = ssh_userauth_list(ssh, NULL);
}
if (auth_bit_mask & SSH_AUTH_METHOD_PUBLICKEY) {
char *p = opts->passphrase ? opts->passphrase : NULL;
if (ssh_userauth_publickey_auto(ssh, NULL, p) == SSH_AUTH_SUCCESS)
return 0;
}
if (auth_bit_mask & SSH_AUTH_METHOD_PASSWORD) {
if (!opts->password) {
char buf[128] = {};
if (ssh_getpass("Password: ", buf, sizeof(buf), 0, 0) < 0) {
priv_set_errv("ssh_getpass failed");
return -1;
}
if (!(opts->password = strndup(buf, sizeof(buf)))) {
priv_set_errv("strndup: %s", strerrno());
return -1;
}
}
if (ssh_userauth_password(ssh, NULL, opts->password) == SSH_AUTH_SUCCESS)
return 0;
}
auth_bit_mask = ssh_userauth_list(ssh, NULL);
if (auth_bit_mask & SSH_AUTH_METHOD_INTERACTIVE) {
if (ssh_authenticate_kbdint(ssh) == SSH_AUTH_SUCCESS)
return 0;
}
return -1;
}
static int ssh_cache_passphrase(const char *prompt, char *buf, size_t len, int echo,
int verify, void *userdata)
{
struct mscp_ssh_opts *opts = userdata;
/* This function is called on the first time for importing
* priv key file with passphrase. It is not called on the
* second time or after because cached passphrase is passed
* to ssh_userauth_publickey_auto(). */
/* ToDo: use
* ssh_userauth_publickey_auto_get_current_identity() to print
* id for which we ask passphrase */
if (ssh_getpass("Passphrase: ", buf, len, echo, verify) < 0)
return -1;
/* cache the passphrase */
if (opts->passphrase)
free(opts->passphrase);
if (!(opts->passphrase = strndup(buf, len))) {
priv_set_errv("strndup: %s", strerrno());
return -1;
}
return 0;
}
static struct ssh_callbacks_struct cb = {
.auth_function = ssh_cache_passphrase,
.userdata = NULL,
};
static ssh_session ssh_init_session(const char *sshdst, struct mscp_ssh_opts *opts)
{
ssh_session ssh = ssh_new();
ssh_callbacks_init(&cb);
cb.userdata = opts;
ssh_set_callbacks(ssh, &cb);
if (ssh_options_set(ssh, SSH_OPTIONS_HOST, sshdst) != SSH_OK) {
priv_set_errv("failed to set destination host");
goto free_out;
}
if (ssh_set_opts(ssh, opts) != 0)
goto free_out;
if (ssh_connect(ssh) != SSH_OK) {
priv_set_errv("failed to connect ssh server: %s", ssh_get_error(ssh));
goto free_out;
}
if (ssh_authenticate(ssh, opts) != 0) {
priv_set_errv("authentication failed: %s", ssh_get_error(ssh));
goto disconnect_out;
}
if (ssh_verify_known_hosts(ssh) != 0) {
priv_set_errv("ssh_veriy_known_hosts failed");
goto disconnect_out;
}
return ssh;
disconnect_out:
ssh_disconnect(ssh);
free_out:
ssh_free(ssh);
return NULL;
}
sftp_session ssh_init_sftp_session(const char *sshdst, struct mscp_ssh_opts *opts)
{
sftp_session sftp;
ssh_session ssh = ssh_init_session(sshdst, opts);
if (!ssh)
return NULL;
sftp = sftp_new(ssh);
if (!sftp) {
priv_set_errv("failed to allocate sftp session: %s", ssh_get_error(ssh));
goto err_out;
}
if (sftp_init(sftp) != SSH_OK) {
priv_set_errv("failed to initialize sftp session: err code %d",
sftp_get_error(sftp));
goto err_out;
}
return sftp;
err_out:
ssh_disconnect(ssh);
ssh_free(ssh);
return NULL;
}
/* copied from https://api.libssh.org/stable/libssh_tutor_guided_tour.html*/
static int ssh_verify_known_hosts(ssh_session session)
{
enum ssh_known_hosts_e state;
unsigned char *hash = NULL;
ssh_key srv_pubkey = NULL;
size_t hlen;
char buf[10];
char *hexa;
char *p;
int cmp;
int rc;
rc = ssh_get_server_publickey(session, &srv_pubkey);
if (rc < 0) {
return -1;
}
rc = ssh_get_publickey_hash(srv_pubkey, SSH_PUBLICKEY_HASH_SHA1, &hash, &hlen);
ssh_key_free(srv_pubkey);
if (rc < 0) {
return -1;
}
state = ssh_session_is_known_server(session);
switch (state) {
case SSH_KNOWN_HOSTS_OK:
/* OK */
break;
case SSH_KNOWN_HOSTS_CHANGED:
fprintf(stderr, "Host key for server changed: it is now:\n");
//ssh_print_hexa("Public key hash", hash, hlen);
fprintf(stderr, "For security reasons, connection will be stopped\n");
ssh_clean_pubkey_hash(&hash);
return -1;
case SSH_KNOWN_HOSTS_OTHER:
fprintf(stderr, "The host key for this server was not found but an other"
"type of key exists.\n");
fprintf(stderr,
"An attacker might change the default server key to"
"confuse your client into thinking the key does not exist\n");
ssh_clean_pubkey_hash(&hash);
return -1;
case SSH_KNOWN_HOSTS_NOT_FOUND:
fprintf(stderr, "Could not find known host file.\n");
fprintf(stderr, "If you accept the host key here, the file will be"
"automatically created.\n");
/* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */
case SSH_KNOWN_HOSTS_UNKNOWN:
hexa = ssh_get_hexa(hash, hlen);
fprintf(stderr, "The server is unknown. Do you trust the host key?\n");
fprintf(stderr, "Public key hash: %s\n", hexa);
fprintf(stderr, "(yes/no): ");
ssh_string_free_char(hexa);
ssh_clean_pubkey_hash(&hash);
p = fgets(buf, sizeof(buf), stdin);
if (p == NULL) {
return -1;
}
cmp = strncasecmp(buf, "yes", 3);
if (cmp != 0) {
return -1;
}
rc = ssh_session_update_known_hosts(session);
if (rc < 0) {
priv_set_errv("%s", ssh_get_error(session));
return -1;
}
break;
case SSH_KNOWN_HOSTS_ERROR:
fprintf(stderr, "known hosts error: %s", ssh_get_error(session));
ssh_clean_pubkey_hash(&hash);
return -1;
}
ssh_clean_pubkey_hash(&hash);
return 0;
}
static int ssh_authenticate_kbdint(ssh_session ssh)
{
/* Copied and bit modified from
* https://api.libssh.org/stable/libssh_tutor_authentication.html */
int rc;
rc = ssh_userauth_kbdint(ssh, NULL, NULL);
while (rc == SSH_AUTH_INFO) {
const char *name, *instruction;
int nprompts, iprompt;
name = ssh_userauth_kbdint_getname(ssh);
instruction = ssh_userauth_kbdint_getinstruction(ssh);
nprompts = ssh_userauth_kbdint_getnprompts(ssh);
if (strlen(name) > 0)
printf("%s\n", name);
if (strlen(instruction) > 0)
printf("%s\n", instruction);
for (iprompt = 0; iprompt < nprompts; iprompt++) {
const char *prompt;
char echo;
prompt = ssh_userauth_kbdint_getprompt(ssh, iprompt, &echo);
if (echo) {
char buf[128], *ptr;
printf("%s", prompt);
if (fgets(buf, sizeof(buf), stdin) == NULL)
return SSH_AUTH_ERROR;
buf[sizeof(buf) - 1] = '\0';
if ((ptr = strchr(buf, '\n')) != NULL)
*ptr = '\0';
if (ssh_userauth_kbdint_setanswer(ssh, iprompt, buf) < 0)
return SSH_AUTH_ERROR;
memset(buf, 0, strlen(buf));
} else {
char *ptr;
ptr = getpass(prompt);
if (ssh_userauth_kbdint_setanswer(ssh, iprompt, ptr) < 0)
return SSH_AUTH_ERROR;
}
}
rc = ssh_userauth_kbdint(ssh, NULL, NULL);
}
return rc;
}
void ssh_sftp_close(sftp_session sftp)
{
ssh_session ssh = sftp_ssh(sftp);
/* XXX: sftp_free is stuck in ssh_poll_ctx_dopoll() when build type is Release.
* skip sftp_free inappropriately...
*/
//sftp_free(sftp);
ssh_disconnect(ssh);
ssh_free(ssh);
}
const char **mscp_ssh_ciphers(void)
{
return ssh_ciphers();
}
const char **mscp_ssh_hmacs(void)
{
return ssh_hmacs();
}