#include "../curl_setup.h"
#ifdef USE_LIBSSH2
#include <limits.h>
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef __VMS
#include <in.h>
#include <inet.h>
#endif
#include <curl/curl.h>
#include "../urldata.h"
#include "../sendf.h"
#include "../hostip.h"
#include "../progress.h"
#include "../transfer.h"
#include "../http.h"
#include "ssh.h"
#include "../url.h"
#include "../speedcheck.h"
#include "../vtls/vtls.h"
#include "../cfilters.h"
#include "../connect.h"
#include "../parsedate.h"
#include "../sockaddr.h"
#include "../multiif.h"
#include "../select.h"
#include "../curlx/fopen.h"
#include "../curlx/warnless.h"
#include "curl_path.h"
#include "../curlx/strparse.h"
#include "../curlx/base64.h"
#include "../curl_memory.h"
#include "../memdebug.h"
static const char *sftp_libssh2_strerror(unsigned long err);
static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc);
static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc);
static LIBSSH2_FREE_FUNC(my_libssh2_free);
static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data,
struct ssh_conn *sshc);
static CURLcode ssh_connect(struct Curl_easy *data, bool *done);
static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done);
static CURLcode ssh_do(struct Curl_easy *data, bool *done);
static CURLcode scp_done(struct Curl_easy *data, CURLcode c, bool premature);
static CURLcode scp_doing(struct Curl_easy *data, bool *dophase_done);
static CURLcode scp_disconnect(struct Curl_easy *data,
struct connectdata *conn, bool dead_connection);
static CURLcode sftp_done(struct Curl_easy *data, CURLcode, bool premature);
static CURLcode sftp_doing(struct Curl_easy *data, bool *dophase_done);
static CURLcode sftp_disconnect(struct Curl_easy *data,
struct connectdata *conn, bool dead);
static CURLcode sftp_perform(struct Curl_easy *data, bool *connected,
bool *dophase_done);
static CURLcode ssh_pollset(struct Curl_easy *data,
struct easy_pollset *ps);
static CURLcode ssh_setup_connection(struct Curl_easy *data,
struct connectdata *conn);
static void ssh_attach(struct Curl_easy *data, struct connectdata *conn);
static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data,
bool block);
const struct Curl_handler Curl_handler_scp = {
"SCP",
ssh_setup_connection,
ssh_do,
scp_done,
ZERO_NULL,
ssh_connect,
ssh_multi_statemach,
scp_doing,
ssh_pollset,
ssh_pollset,
ZERO_NULL,
ssh_pollset,
scp_disconnect,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ssh_attach,
ZERO_NULL,
PORT_SSH,
CURLPROTO_SCP,
CURLPROTO_SCP,
PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION
| PROTOPT_NOURLQUERY
};
const struct Curl_handler Curl_handler_sftp = {
"SFTP",
ssh_setup_connection,
ssh_do,
sftp_done,
ZERO_NULL,
ssh_connect,
ssh_multi_statemach,
sftp_doing,
ssh_pollset,
ssh_pollset,
ZERO_NULL,
ssh_pollset,
sftp_disconnect,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ssh_attach,
ZERO_NULL,
PORT_SSH,
CURLPROTO_SFTP,
CURLPROTO_SFTP,
PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION
| PROTOPT_NOURLQUERY
};
static void
kbd_callback(const char *name, int name_len, const char *instruction,
int instruction_len, int num_prompts,
const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
void **abstract)
{
struct Curl_easy *data = (struct Curl_easy *)*abstract;
#ifdef CURL_LIBSSH2_DEBUG
curl_mfprintf(stderr, "name=%s\n", name);
curl_mfprintf(stderr, "name_len=%d\n", name_len);
curl_mfprintf(stderr, "instruction=%s\n", instruction);
curl_mfprintf(stderr, "instruction_len=%d\n", instruction_len);
curl_mfprintf(stderr, "num_prompts=%d\n", num_prompts);
#else
(void)name;
(void)name_len;
(void)instruction;
(void)instruction_len;
#endif
if(num_prompts == 1) {
struct connectdata *conn = data->conn;
responses[0].text = strdup(conn->passwd);
responses[0].length =
responses[0].text == NULL ? 0 : curlx_uztoui(strlen(conn->passwd));
}
(void)prompts;
}
static CURLcode sftp_libssh2_error_to_CURLE(unsigned long err)
{
switch(err) {
case LIBSSH2_FX_OK:
return CURLE_OK;
case LIBSSH2_FX_NO_SUCH_FILE:
case LIBSSH2_FX_NO_SUCH_PATH:
return CURLE_REMOTE_FILE_NOT_FOUND;
case LIBSSH2_FX_PERMISSION_DENIED:
case LIBSSH2_FX_WRITE_PROTECT:
case LIBSSH2_FX_LOCK_CONFlICT:
return CURLE_REMOTE_ACCESS_DENIED;
case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM:
case LIBSSH2_FX_QUOTA_EXCEEDED:
return CURLE_REMOTE_DISK_FULL;
case LIBSSH2_FX_FILE_ALREADY_EXISTS:
return CURLE_REMOTE_FILE_EXISTS;
case LIBSSH2_FX_DIR_NOT_EMPTY:
return CURLE_QUOTE_ERROR;
default:
break;
}
return CURLE_SSH;
}
static CURLcode libssh2_session_error_to_CURLE(int err)
{
switch(err) {
case LIBSSH2_ERROR_NONE:
return CURLE_OK;
case LIBSSH2_ERROR_SCP_PROTOCOL:
return CURLE_REMOTE_FILE_NOT_FOUND;
case LIBSSH2_ERROR_SOCKET_NONE:
return CURLE_COULDNT_CONNECT;
case LIBSSH2_ERROR_ALLOC:
return CURLE_OUT_OF_MEMORY;
case LIBSSH2_ERROR_SOCKET_SEND:
return CURLE_SEND_ERROR;
case LIBSSH2_ERROR_HOSTKEY_INIT:
case LIBSSH2_ERROR_HOSTKEY_SIGN:
case LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED:
case LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED:
return CURLE_PEER_FAILED_VERIFICATION;
case LIBSSH2_ERROR_PASSWORD_EXPIRED:
return CURLE_LOGIN_DENIED;
case LIBSSH2_ERROR_SOCKET_TIMEOUT:
case LIBSSH2_ERROR_TIMEOUT:
return CURLE_OPERATION_TIMEDOUT;
case LIBSSH2_ERROR_EAGAIN:
return CURLE_AGAIN;
}
return CURLE_SSH;
}
static LIBSSH2_ALLOC_FUNC(my_libssh2_malloc)
{
(void)abstract;
return Curl_cmalloc(count);
}
static LIBSSH2_REALLOC_FUNC(my_libssh2_realloc)
{
(void)abstract;
return Curl_crealloc(ptr, count);
}
static LIBSSH2_FREE_FUNC(my_libssh2_free)
{
(void)abstract;
if(ptr)
Curl_cfree(ptr);
}
static void myssh_state(struct Curl_easy *data,
struct ssh_conn *sshc,
sshstate nowstate)
{
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
static const char * const names[] = {
"SSH_STOP",
"SSH_INIT",
"SSH_S_STARTUP",
"SSH_HOSTKEY",
"SSH_AUTHLIST",
"SSH_AUTH_PKEY_INIT",
"SSH_AUTH_PKEY",
"SSH_AUTH_PASS_INIT",
"SSH_AUTH_PASS",
"SSH_AUTH_AGENT_INIT",
"SSH_AUTH_AGENT_LIST",
"SSH_AUTH_AGENT",
"SSH_AUTH_HOST_INIT",
"SSH_AUTH_HOST",
"SSH_AUTH_KEY_INIT",
"SSH_AUTH_KEY",
"SSH_AUTH_GSSAPI",
"SSH_AUTH_DONE",
"SSH_SFTP_INIT",
"SSH_SFTP_REALPATH",
"SSH_SFTP_QUOTE_INIT",
"SSH_SFTP_POSTQUOTE_INIT",
"SSH_SFTP_QUOTE",
"SSH_SFTP_NEXT_QUOTE",
"SSH_SFTP_QUOTE_STAT",
"SSH_SFTP_QUOTE_SETSTAT",
"SSH_SFTP_QUOTE_SYMLINK",
"SSH_SFTP_QUOTE_MKDIR",
"SSH_SFTP_QUOTE_RENAME",
"SSH_SFTP_QUOTE_RMDIR",
"SSH_SFTP_QUOTE_UNLINK",
"SSH_SFTP_QUOTE_STATVFS",
"SSH_SFTP_GETINFO",
"SSH_SFTP_FILETIME",
"SSH_SFTP_TRANS_INIT",
"SSH_SFTP_UPLOAD_INIT",
"SSH_SFTP_CREATE_DIRS_INIT",
"SSH_SFTP_CREATE_DIRS",
"SSH_SFTP_CREATE_DIRS_MKDIR",
"SSH_SFTP_READDIR_INIT",
"SSH_SFTP_READDIR",
"SSH_SFTP_READDIR_LINK",
"SSH_SFTP_READDIR_BOTTOM",
"SSH_SFTP_READDIR_DONE",
"SSH_SFTP_DOWNLOAD_INIT",
"SSH_SFTP_DOWNLOAD_STAT",
"SSH_SFTP_CLOSE",
"SSH_SFTP_SHUTDOWN",
"SSH_SCP_TRANS_INIT",
"SSH_SCP_UPLOAD_INIT",
"SSH_SCP_DOWNLOAD_INIT",
"SSH_SCP_DOWNLOAD",
"SSH_SCP_DONE",
"SSH_SCP_SEND_EOF",
"SSH_SCP_WAIT_EOF",
"SSH_SCP_WAIT_CLOSE",
"SSH_SCP_CHANNEL_FREE",
"SSH_SESSION_DISCONNECT",
"SSH_SESSION_FREE",
"QUIT"
};
DEBUGASSERT(CURL_ARRAYSIZE(names) == SSH_LAST);
if(sshc->state != nowstate) {
infof(data, "SFTP %p state change from %s to %s",
(void *)sshc, names[sshc->state], names[nowstate]);
}
#endif
(void)data;
sshc->state = nowstate;
}
static int sshkeycallback(CURL *easy,
const struct curl_khkey *knownkey,
const struct curl_khkey *foundkey,
enum curl_khmatch match,
void *clientp)
{
(void)easy;
(void)knownkey;
(void)foundkey;
(void)clientp;
return (match != CURLKHMATCH_OK) ? CURLKHSTAT_REJECT : CURLKHSTAT_FINE;
}
static enum curl_khtype convert_ssh2_keytype(int sshkeytype)
{
enum curl_khtype keytype = CURLKHTYPE_UNKNOWN;
switch(sshkeytype) {
case LIBSSH2_HOSTKEY_TYPE_RSA:
keytype = CURLKHTYPE_RSA;
break;
case LIBSSH2_HOSTKEY_TYPE_DSS:
keytype = CURLKHTYPE_DSS;
break;
#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_256
case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
keytype = CURLKHTYPE_ECDSA;
break;
#endif
#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_384
case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
keytype = CURLKHTYPE_ECDSA;
break;
#endif
#ifdef LIBSSH2_HOSTKEY_TYPE_ECDSA_521
case LIBSSH2_HOSTKEY_TYPE_ECDSA_521:
keytype = CURLKHTYPE_ECDSA;
break;
#endif
#ifdef LIBSSH2_HOSTKEY_TYPE_ED25519
case LIBSSH2_HOSTKEY_TYPE_ED25519:
keytype = CURLKHTYPE_ED25519;
break;
#endif
}
return keytype;
}
static CURLcode ssh_knownhost(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int sshkeytype = 0;
size_t keylen = 0;
int rc = 0;
CURLcode result = CURLE_OK;
if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
struct connectdata *conn = data->conn;
struct libssh2_knownhost *host = NULL;
const char *remotekey = libssh2_session_hostkey(sshc->ssh_session,
&keylen, &sshkeytype);
int keycheck = LIBSSH2_KNOWNHOST_CHECK_FAILURE;
int keybit = 0;
if(remotekey) {
enum curl_khmatch keymatch;
curl_sshkeycallback func =
data->set.ssh_keyfunc ? data->set.ssh_keyfunc : sshkeycallback;
struct curl_khkey knownkey;
struct curl_khkey *knownkeyp = NULL;
struct curl_khkey foundkey;
switch(sshkeytype) {
case LIBSSH2_HOSTKEY_TYPE_RSA:
keybit = LIBSSH2_KNOWNHOST_KEY_SSHRSA;
break;
case LIBSSH2_HOSTKEY_TYPE_DSS:
keybit = LIBSSH2_KNOWNHOST_KEY_SSHDSS;
break;
case LIBSSH2_HOSTKEY_TYPE_ECDSA_256:
keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_256;
break;
case LIBSSH2_HOSTKEY_TYPE_ECDSA_384:
keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_384;
break;
case LIBSSH2_HOSTKEY_TYPE_ECDSA_521:
keybit = LIBSSH2_KNOWNHOST_KEY_ECDSA_521;
break;
case LIBSSH2_HOSTKEY_TYPE_ED25519:
keybit = LIBSSH2_KNOWNHOST_KEY_ED25519;
break;
default:
infof(data, "unsupported key type, cannot check knownhosts");
keybit = 0;
break;
}
if(!keybit)
rc = CURLKHSTAT_REJECT;
else {
keycheck = libssh2_knownhost_checkp(sshc->kh,
conn->host.name,
(conn->remote_port != PORT_SSH) ?
conn->remote_port : -1,
remotekey, keylen,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW|
keybit,
&host);
infof(data, "SSH host check: %d, key: %s", keycheck,
(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) ?
host->key : "<none>");
if(keycheck <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH) {
knownkey.key = host->key;
knownkey.len = 0;
knownkey.keytype = convert_ssh2_keytype(sshkeytype);
knownkeyp = &knownkey;
}
foundkey.key = remotekey;
foundkey.len = keylen;
foundkey.keytype = convert_ssh2_keytype(sshkeytype);
keymatch = (enum curl_khmatch)keycheck;
Curl_set_in_callback(data, TRUE);
rc = func(data, knownkeyp,
&foundkey,
keymatch, data->set.ssh_keyfunc_userp);
Curl_set_in_callback(data, FALSE);
}
}
else
rc = CURLKHSTAT_REJECT;
switch(rc) {
default:
case CURLKHSTAT_REJECT:
myssh_state(data, sshc, SSH_SESSION_FREE);
FALLTHROUGH();
case CURLKHSTAT_DEFER:
result = CURLE_PEER_FAILED_VERIFICATION;
break;
case CURLKHSTAT_FINE_REPLACE:
if(host)
libssh2_knownhost_del(sshc->kh, host);
FALLTHROUGH();
case CURLKHSTAT_FINE:
case CURLKHSTAT_FINE_ADD_TO_FILE:
if(keycheck != LIBSSH2_KNOWNHOST_CHECK_MATCH) {
int addrc = libssh2_knownhost_add(sshc->kh,
conn->host.name, NULL,
remotekey, keylen,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW|
keybit, NULL);
if(addrc)
infof(data, "WARNING: adding the known host %s failed",
conn->host.name);
else if(rc == CURLKHSTAT_FINE_ADD_TO_FILE ||
rc == CURLKHSTAT_FINE_REPLACE) {
int wrc =
libssh2_knownhost_writefile(sshc->kh,
data->set.str[STRING_SSH_KNOWNHOSTS],
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
if(wrc) {
infof(data, "WARNING: writing %s failed",
data->set.str[STRING_SSH_KNOWNHOSTS]);
}
}
}
break;
}
}
return result;
}
static CURLcode ssh_check_fingerprint(struct Curl_easy *data,
struct ssh_conn *sshc)
{
const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5];
const char *pubkey_sha256 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256];
infof(data, "SSH MD5 public key: %s",
pubkey_md5 != NULL ? pubkey_md5 : "NULL");
infof(data, "SSH SHA256 public key: %s",
pubkey_sha256 != NULL ? pubkey_sha256 : "NULL");
if(pubkey_sha256) {
const char *fingerprint = NULL;
char *fingerprint_b64 = NULL;
size_t fingerprint_b64_len;
size_t pub_pos = 0;
size_t b64_pos = 0;
fingerprint = libssh2_hostkey_hash(sshc->ssh_session,
LIBSSH2_HOSTKEY_HASH_SHA256);
if(!fingerprint) {
failf(data,
"Denied establishing ssh session: sha256 fingerprint "
"not available");
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_PEER_FAILED_VERIFICATION;
}
if(curlx_base64_encode(fingerprint, 32, &fingerprint_b64,
&fingerprint_b64_len) != CURLE_OK) {
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_PEER_FAILED_VERIFICATION;
}
if(!fingerprint_b64) {
failf(data, "sha256 fingerprint could not be encoded");
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_PEER_FAILED_VERIFICATION;
}
infof(data, "SSH SHA256 fingerprint: %s", fingerprint_b64);
while((pubkey_sha256[pub_pos] != '=') && pubkey_sha256[pub_pos]) {
pub_pos++;
}
while((fingerprint_b64[b64_pos] != '=') && fingerprint_b64[b64_pos]) {
b64_pos++;
}
if((pub_pos != b64_pos) ||
strncmp(fingerprint_b64, pubkey_sha256, pub_pos)) {
failf(data,
"Denied establishing ssh session: mismatch sha256 fingerprint. "
"Remote %s is not equal to %s", fingerprint_b64, pubkey_sha256);
free(fingerprint_b64);
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_PEER_FAILED_VERIFICATION;
}
free(fingerprint_b64);
infof(data, "SHA256 checksum match");
}
if(pubkey_md5) {
char md5buffer[33];
const char *fingerprint;
fingerprint = libssh2_hostkey_hash(sshc->ssh_session,
LIBSSH2_HOSTKEY_HASH_MD5);
if(fingerprint) {
int i;
for(i = 0; i < 16; i++) {
curl_msnprintf(&md5buffer[i*2], 3, "%02x",
(unsigned char)fingerprint[i]);
}
infof(data, "SSH MD5 fingerprint: %s", md5buffer);
}
if(!fingerprint || !curl_strequal(md5buffer, pubkey_md5)) {
if(fingerprint) {
failf(data,
"Denied establishing ssh session: mismatch md5 fingerprint. "
"Remote %s is not equal to %s", md5buffer, pubkey_md5);
}
else {
failf(data,
"Denied establishing ssh session: md5 fingerprint "
"not available");
}
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_PEER_FAILED_VERIFICATION;
}
infof(data, "MD5 checksum match");
}
if(!pubkey_md5 && !pubkey_sha256) {
if(data->set.ssh_hostkeyfunc) {
size_t keylen = 0;
int sshkeytype = 0;
int rc = 0;
const char *remotekey = libssh2_session_hostkey(sshc->ssh_session,
&keylen, &sshkeytype);
if(remotekey) {
enum curl_khtype keytype = convert_ssh2_keytype(sshkeytype);
Curl_set_in_callback(data, TRUE);
rc = data->set.ssh_hostkeyfunc(data->set.ssh_hostkeyfunc_userp,
(int)keytype, remotekey, keylen);
Curl_set_in_callback(data, FALSE);
if(rc!= CURLKHMATCH_OK) {
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_PEER_FAILED_VERIFICATION;
}
}
else {
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_PEER_FAILED_VERIFICATION;
}
return CURLE_OK;
}
else {
return ssh_knownhost(data, sshc);
}
}
else {
return CURLE_OK;
}
}
static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data,
struct ssh_conn *sshc)
{
CURLcode result = CURLE_OK;
static const char * const hostkey_method_ssh_ed25519
= "ssh-ed25519";
static const char * const hostkey_method_ssh_ecdsa_521
= "ecdsa-sha2-nistp521";
static const char * const hostkey_method_ssh_ecdsa_384
= "ecdsa-sha2-nistp384";
static const char * const hostkey_method_ssh_ecdsa_256
= "ecdsa-sha2-nistp256";
static const char * const hostkey_method_ssh_rsa_all
= "rsa-sha2-256,rsa-sha2-512,ssh-rsa";
static const char * const hostkey_method_ssh_dss
= "ssh-dss";
const char *hostkey_method = NULL;
struct connectdata *conn = data->conn;
struct libssh2_knownhost* store = NULL;
const char *kh_name_end = NULL;
size_t kh_name_size = 0;
int port = 0;
bool found = FALSE;
if(sshc->kh &&
!data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5] &&
!data->set.str[STRING_SSH_HOST_PUBLIC_KEY_SHA256]) {
while(!libssh2_knownhost_get(sshc->kh, &store, store)) {
if(store) {
if(store->name) {
if(store->name[0] == '[') {
kh_name_end = strstr(store->name, "]:");
if(!kh_name_end) {
infof(data, "Invalid host pattern %s in %s",
store->name, data->set.str[STRING_SSH_KNOWNHOSTS]);
continue;
}
port = atoi(kh_name_end + 2);
if(kh_name_end && (port == conn->remote_port)) {
kh_name_size = strlen(store->name) - 1 - strlen(kh_name_end);
if(strncmp(store->name + 1,
conn->host.name, kh_name_size) == 0) {
found = TRUE;
break;
}
}
}
else if(strcmp(store->name, conn->host.name) == 0) {
found = TRUE;
break;
}
}
else {
found = TRUE;
break;
}
}
}
if(found) {
int rc;
infof(data, "Found host %s in %s",
conn->host.name, data->set.str[STRING_SSH_KNOWNHOSTS]);
switch(store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK) {
case LIBSSH2_KNOWNHOST_KEY_ED25519:
hostkey_method = hostkey_method_ssh_ed25519;
break;
case LIBSSH2_KNOWNHOST_KEY_ECDSA_521:
hostkey_method = hostkey_method_ssh_ecdsa_521;
break;
case LIBSSH2_KNOWNHOST_KEY_ECDSA_384:
hostkey_method = hostkey_method_ssh_ecdsa_384;
break;
case LIBSSH2_KNOWNHOST_KEY_ECDSA_256:
hostkey_method = hostkey_method_ssh_ecdsa_256;
break;
case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
hostkey_method = hostkey_method_ssh_rsa_all;
break;
case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
hostkey_method = hostkey_method_ssh_dss;
break;
case LIBSSH2_KNOWNHOST_KEY_RSA1:
failf(data, "Found host key type RSA1 which is not supported");
return CURLE_SSH;
default:
failf(data, "Unknown host key type: %i",
(store->typemask & LIBSSH2_KNOWNHOST_KEY_MASK));
return CURLE_SSH;
}
infof(data, "Set \"%s\" as SSH hostkey type", hostkey_method);
rc = libssh2_session_method_pref(sshc->ssh_session,
LIBSSH2_METHOD_HOSTKEY, hostkey_method);
if(rc) {
char *errmsg = NULL;
int errlen;
libssh2_session_last_error(sshc->ssh_session, &errmsg, &errlen, 0);
failf(data, "libssh2 method '%s' failed: %s", hostkey_method, errmsg);
result = libssh2_session_error_to_CURLE(rc);
}
}
else {
infof(data, "Did not find host %s in %s",
conn->host.name, data->set.str[STRING_SSH_KNOWNHOSTS]);
}
}
return result;
}
static CURLcode return_quote_error(struct Curl_easy *data,
struct ssh_conn *sshc)
{
failf(data, "Suspicious data after the command line");
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
return CURLE_QUOTE_ERROR;
}
static CURLcode sftp_quote(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
const char *cp;
CURLcode result = CURLE_OK;
char *cmd = sshc->quote_item->data;
sshc->acceptfail = FALSE;
if(cmd[0] == '*') {
cmd++;
sshc->acceptfail = TRUE;
}
if(curl_strequal("pwd", cmd)) {
char *tmp = curl_maprintf("257 \"%s\" is current directory.\n",
sshp->path);
if(!tmp)
return CURLE_OUT_OF_MEMORY;
Curl_debug(data, CURLINFO_HEADER_OUT, "PWD\n", 4);
Curl_debug(data, CURLINFO_HEADER_IN, tmp, strlen(tmp));
result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp));
free(tmp);
if(!result)
myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE);
return result;
}
cp = strchr(cmd, ' ');
if(!cp) {
failf(data, "Syntax error command '%s', missing parameter", cmd);
return result;
}
result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir);
if(result) {
if(result != CURLE_OUT_OF_MEMORY)
failf(data, "Syntax error: Bad first parameter to '%s'", cmd);
return result;
}
if(!strncmp(cmd, "chgrp ", 6) ||
!strncmp(cmd, "chmod ", 6) ||
!strncmp(cmd, "chown ", 6) ||
!strncmp(cmd, "atime ", 6) ||
!strncmp(cmd, "mtime ", 6)) {
result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir);
if(result) {
if(result != CURLE_OUT_OF_MEMORY)
failf(data, "Syntax error in %s: Bad second parameter", cmd);
Curl_safefree(sshc->quote_path1);
return result;
}
if(*cp)
return_quote_error(data, sshc);
memset(&sshp->quote_attrs, 0, sizeof(LIBSSH2_SFTP_ATTRIBUTES));
myssh_state(data, sshc, SSH_SFTP_QUOTE_STAT);
return result;
}
if(!strncmp(cmd, "ln ", 3) ||
!strncmp(cmd, "symlink ", 8)) {
result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir);
if(result) {
if(result != CURLE_OUT_OF_MEMORY)
failf(data, "Syntax error in ln/symlink: Bad second parameter");
Curl_safefree(sshc->quote_path1);
return result;
}
if(*cp)
return_quote_error(data, sshc);
myssh_state(data, sshc, SSH_SFTP_QUOTE_SYMLINK);
return result;
}
else if(!strncmp(cmd, "mkdir ", 6)) {
if(*cp)
return_quote_error(data, sshc);
myssh_state(data, sshc, SSH_SFTP_QUOTE_MKDIR);
return result;
}
else if(!strncmp(cmd, "rename ", 7)) {
result = Curl_get_pathname(&cp, &sshc->quote_path2, sshc->homedir);
if(result) {
if(result != CURLE_OUT_OF_MEMORY)
failf(data, "Syntax error in rename: Bad second parameter");
Curl_safefree(sshc->quote_path1);
return result;
}
if(*cp)
return_quote_error(data, sshc);
myssh_state(data, sshc, SSH_SFTP_QUOTE_RENAME);
return result;
}
else if(!strncmp(cmd, "rmdir ", 6)) {
if(*cp)
return_quote_error(data, sshc);
myssh_state(data, sshc, SSH_SFTP_QUOTE_RMDIR);
return result;
}
else if(!strncmp(cmd, "rm ", 3)) {
if(*cp)
return_quote_error(data, sshc);
myssh_state(data, sshc, SSH_SFTP_QUOTE_UNLINK);
return result;
}
else if(!strncmp(cmd, "statvfs ", 8)) {
if(*cp)
return_quote_error(data, sshc);
myssh_state(data, sshc, SSH_SFTP_QUOTE_STATVFS);
return result;
}
failf(data, "Unknown SFTP command");
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
return CURLE_QUOTE_ERROR;
}
static CURLcode
sftp_upload_init(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp,
bool *blockp)
{
unsigned long flags;
if(data->state.resume_from) {
LIBSSH2_SFTP_ATTRIBUTES attrs;
if(data->state.resume_from < 0) {
int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
LIBSSH2_SFTP_STAT, &attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
*blockp = TRUE;
return CURLE_OK;
}
if(rc) {
data->state.resume_from = 0;
}
else {
curl_off_t size = attrs.filesize;
if(size < 0) {
failf(data, "Bad file size (%" FMT_OFF_T ")", size);
return CURLE_BAD_DOWNLOAD_RESUME;
}
data->state.resume_from = attrs.filesize;
}
}
}
if(data->set.remote_append) {
flags = LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_APPEND;
}
else if(data->state.resume_from > 0) {
flags = LIBSSH2_FXF_WRITE;
}
else {
flags = LIBSSH2_FXF_WRITE | LIBSSH2_FXF_CREAT | LIBSSH2_FXF_TRUNC;
}
sshc->sftp_handle =
libssh2_sftp_open_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
flags, (long)data->set.new_file_perms,
LIBSSH2_SFTP_OPENFILE);
if(!sshc->sftp_handle) {
CURLcode result;
unsigned long sftperr;
int rc = libssh2_session_last_errno(sshc->ssh_session);
if(LIBSSH2_ERROR_EAGAIN == rc) {
*blockp = TRUE;
return CURLE_OK;
}
if(LIBSSH2_ERROR_SFTP_PROTOCOL == rc)
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
else
sftperr = LIBSSH2_FX_OK;
if(sshc->secondCreateDirs) {
myssh_state(data, sshc, SSH_SFTP_CLOSE);
failf(data, "Creating the dir/file failed: %s",
sftp_libssh2_strerror(sftperr));
return sftp_libssh2_error_to_CURLE(sftperr);
}
if(((sftperr == LIBSSH2_FX_NO_SUCH_FILE) ||
(sftperr == LIBSSH2_FX_FAILURE) ||
(sftperr == LIBSSH2_FX_NO_SUCH_PATH)) &&
(data->set.ftp_create_missing_dirs &&
(strlen(sshp->path) > 1))) {
sshc->secondCreateDirs = 1;
myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_INIT);
return CURLE_OK;
}
myssh_state(data, sshc, SSH_SFTP_CLOSE);
result = sftp_libssh2_error_to_CURLE(sftperr);
if(!result) {
result = CURLE_SSH;
sftperr = LIBSSH2_FX_OK;
}
failf(data, "Upload failed: %s (%lu/%d)",
sftperr != LIBSSH2_FX_OK ?
sftp_libssh2_strerror(sftperr) : "ssh error",
sftperr, rc);
return result;
}
if(data->state.resume_from > 0 && !data->set.remote_append) {
int seekerr = CURL_SEEKFUNC_OK;
if(data->set.seek_func) {
Curl_set_in_callback(data, TRUE);
seekerr = data->set.seek_func(data->set.seek_client,
data->state.resume_from, SEEK_SET);
Curl_set_in_callback(data, FALSE);
}
if(seekerr != CURL_SEEKFUNC_OK) {
curl_off_t passed = 0;
if(seekerr != CURL_SEEKFUNC_CANTSEEK) {
failf(data, "Could not seek stream");
return CURLE_FTP_COULDNT_USE_REST;
}
do {
char scratch[4*1024];
size_t readthisamountnow =
(data->state.resume_from - passed >
(curl_off_t)sizeof(scratch)) ?
sizeof(scratch) : curlx_sotouz(data->state.resume_from - passed);
size_t actuallyread;
Curl_set_in_callback(data, TRUE);
actuallyread = data->state.fread_func(scratch, 1,
readthisamountnow,
data->state.in);
Curl_set_in_callback(data, FALSE);
passed += actuallyread;
if((actuallyread == 0) || (actuallyread > readthisamountnow)) {
failf(data, "Failed to read data");
return CURLE_FTP_COULDNT_USE_REST;
}
} while(passed < data->state.resume_from);
}
if(data->state.infilesize > 0) {
data->state.infilesize -= data->state.resume_from;
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->state.infilesize);
}
libssh2_sftp_seek64(sshc->sftp_handle,
(libssh2_uint64_t)data->state.resume_from);
}
if(data->state.infilesize > 0) {
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->state.infilesize);
}
Curl_xfer_setup_send(data, FIRSTSOCKET);
data->conn->recv_idx = FIRSTSOCKET;
sshc->orig_waitfor = data->req.keepon;
Curl_multi_mark_dirty(data);
myssh_state(data, sshc, SSH_STOP);
return CURLE_OK;
}
static CURLcode ssh_state_pkey_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
sshc->authed = FALSE;
if((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) &&
(strstr(sshc->authlist, "publickey") != NULL)) {
bool out_of_memory = FALSE;
sshc->rsa_pub = sshc->rsa = NULL;
if(data->set.str[STRING_SSH_PRIVATE_KEY])
sshc->rsa = strdup(data->set.str[STRING_SSH_PRIVATE_KEY]);
else {
char *home = curl_getenv("HOME");
struct_stat sbuf;
if(home) {
sshc->rsa = curl_maprintf("%s/.ssh/id_rsa", home);
if(!sshc->rsa)
out_of_memory = TRUE;
else if(curlx_stat(sshc->rsa, &sbuf)) {
free(sshc->rsa);
sshc->rsa = curl_maprintf("%s/.ssh/id_dsa", home);
if(!sshc->rsa)
out_of_memory = TRUE;
else if(curlx_stat(sshc->rsa, &sbuf)) {
Curl_safefree(sshc->rsa);
}
}
free(home);
}
if(!out_of_memory && !sshc->rsa) {
sshc->rsa = strdup("id_rsa");
if(sshc->rsa && curlx_stat(sshc->rsa, &sbuf)) {
free(sshc->rsa);
sshc->rsa = strdup("id_dsa");
if(sshc->rsa && curlx_stat(sshc->rsa, &sbuf)) {
free(sshc->rsa);
sshc->rsa = strdup("");
}
}
}
}
if(data->set.str[STRING_SSH_PUBLIC_KEY]
&& data->set.str[STRING_SSH_PUBLIC_KEY][0]) {
sshc->rsa_pub = strdup(data->set.str[STRING_SSH_PUBLIC_KEY]);
if(!sshc->rsa_pub)
out_of_memory = TRUE;
}
if(out_of_memory || !sshc->rsa) {
Curl_safefree(sshc->rsa);
Curl_safefree(sshc->rsa_pub);
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_OUT_OF_MEMORY;
}
sshc->passphrase = data->set.ssl.key_passwd;
if(!sshc->passphrase)
sshc->passphrase = "";
if(sshc->rsa_pub)
infof(data, "Using SSH public key file '%s'", sshc->rsa_pub);
infof(data, "Using SSH private key file '%s'", sshc->rsa);
myssh_state(data, sshc, SSH_AUTH_PKEY);
}
else {
myssh_state(data, sshc, SSH_AUTH_PASS_INIT);
}
return 0;
}
static CURLcode
sftp_quote_stat(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp,
bool *blockp)
{
char *cmd = sshc->quote_item->data;
sshc->acceptfail = FALSE;
if(cmd[0] == '*') {
cmd++;
sshc->acceptfail = TRUE;
}
if(!!strncmp(cmd, "chmod", 5)) {
int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2,
curlx_uztoui(strlen(sshc->quote_path2)),
LIBSSH2_SFTP_STAT,
&sshp->quote_attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
*blockp = TRUE;
return CURLE_OK;
}
if(rc && !sshc->acceptfail) {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
failf(data, "Attempt to get SFTP stats failed: %s",
sftp_libssh2_strerror(sftperr));
goto fail;
}
}
if(!strncmp(cmd, "chgrp", 5)) {
const char *p = sshc->quote_path1;
curl_off_t gid;
if(!curlx_str_number(&p, &gid, ULONG_MAX)) {
sshp->quote_attrs.gid = (unsigned long)gid;
sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID;
}
else if(!sshc->acceptfail) {
failf(data, "Syntax error: chgrp gid not a number");
goto fail;
}
}
else if(!strncmp(cmd, "chmod", 5)) {
curl_off_t perms;
const char *p = sshc->quote_path1;
if(curlx_str_octal(&p, &perms, 07777)) {
failf(data, "Syntax error: chmod permissions not a number");
goto fail;
}
sshp->quote_attrs.permissions = (unsigned long)perms;
sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_PERMISSIONS;
}
else if(!strncmp(cmd, "chown", 5)) {
const char *p = sshc->quote_path1;
curl_off_t uid;
if(!curlx_str_number(&p, &uid, ULONG_MAX)) {
sshp->quote_attrs.uid = (unsigned long)uid;
sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_UIDGID;
}
else if(!sshc->acceptfail) {
failf(data, "Syntax error: chown uid not a number");
goto fail;
}
}
else if(!strncmp(cmd, "atime", 5) ||
!strncmp(cmd, "mtime", 5)) {
time_t date;
bool fail = FALSE;
if(Curl_getdate_capped(sshc->quote_path1, &date)) {
failf(data, "incorrect date format for %.*s", 5, cmd);
fail = TRUE;
}
#if SIZEOF_TIME_T > SIZEOF_LONG
if(date > 0xffffffff) {
failf(data, "date overflow");
fail = TRUE;
}
#endif
if(fail)
goto fail;
if(!strncmp(cmd, "atime", 5))
sshp->quote_attrs.atime = (unsigned long)date;
else
sshp->quote_attrs.mtime = (unsigned long)date;
sshp->quote_attrs.flags = LIBSSH2_SFTP_ATTR_ACMODTIME;
}
myssh_state(data, sshc, SSH_SFTP_QUOTE_SETSTAT);
return CURLE_OK;
fail:
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
return CURLE_QUOTE_ERROR;
}
static CURLcode
sftp_download_stat(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp,
bool *blockp)
{
LIBSSH2_SFTP_ATTRIBUTES attrs;
int rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
LIBSSH2_SFTP_STAT, &attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
*blockp = TRUE;
return CURLE_OK;
}
if(rc ||
!(attrs.flags & LIBSSH2_SFTP_ATTR_SIZE) ||
(attrs.filesize == 0)) {
data->req.size = -1;
data->req.maxdownload = -1;
Curl_pgrsSetDownloadSize(data, -1);
attrs.filesize = 0;
}
else {
curl_off_t size = attrs.filesize;
if(size < 0) {
failf(data, "Bad file size (%" FMT_OFF_T ")", size);
return CURLE_BAD_DOWNLOAD_RESUME;
}
if(data->state.use_range) {
curl_off_t from, to;
const char *p = data->state.range;
int to_t, from_t;
from_t = curlx_str_number(&p, &from, CURL_OFF_T_MAX);
if(from_t == STRE_OVERFLOW)
return CURLE_RANGE_ERROR;
curlx_str_passblanks(&p);
(void)curlx_str_single(&p, '-');
to_t = curlx_str_numblanks(&p, &to);
if(to_t == STRE_OVERFLOW)
return CURLE_RANGE_ERROR;
if((to_t == STRE_NO_NUM)
|| (to >= size)) {
to = size - 1;
}
if(from_t) {
from = size - to;
to = size - 1;
}
if(from > size) {
failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%"
FMT_OFF_T ")", from, (curl_off_t)attrs.filesize);
return CURLE_BAD_DOWNLOAD_RESUME;
}
if(from > to) {
from = to;
size = 0;
}
else {
if((to - from) == CURL_OFF_T_MAX)
return CURLE_RANGE_ERROR;
size = to - from + 1;
}
libssh2_sftp_seek64(sshc->sftp_handle, (libssh2_uint64_t)from);
}
data->req.size = size;
data->req.maxdownload = size;
Curl_pgrsSetDownloadSize(data, size);
}
if(data->state.resume_from) {
if(data->state.resume_from < 0) {
if((curl_off_t)attrs.filesize < -data->state.resume_from) {
failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%"
FMT_OFF_T ")",
data->state.resume_from, (curl_off_t)attrs.filesize);
return CURLE_BAD_DOWNLOAD_RESUME;
}
data->state.resume_from += attrs.filesize;
}
else {
if((curl_off_t)attrs.filesize < data->state.resume_from) {
failf(data, "Offset (%" FMT_OFF_T
") was beyond file size (%" FMT_OFF_T ")",
data->state.resume_from, (curl_off_t)attrs.filesize);
return CURLE_BAD_DOWNLOAD_RESUME;
}
}
data->req.size = attrs.filesize - data->state.resume_from;
data->req.maxdownload = attrs.filesize - data->state.resume_from;
Curl_pgrsSetDownloadSize(data,
attrs.filesize - data->state.resume_from);
libssh2_sftp_seek64(sshc->sftp_handle,
(libssh2_uint64_t)data->state.resume_from);
}
if(data->req.size == 0) {
Curl_xfer_setup_nop(data);
infof(data, "File already completely downloaded");
myssh_state(data, sshc, SSH_STOP);
return CURLE_OK;
}
Curl_xfer_setup_recv(data, FIRSTSOCKET, data->req.size);
data->conn->send_idx = 0;
myssh_state(data, sshc, SSH_STOP);
return CURLE_OK;
}
static CURLcode sftp_readdir(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp,
bool *blockp)
{
CURLcode result = CURLE_OK;
int rc = libssh2_sftp_readdir_ex(sshc->sftp_handle,
sshp->readdir_filename, CURL_PATH_MAX,
sshp->readdir_longentry, CURL_PATH_MAX,
&sshp->readdir_attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
*blockp = TRUE;
return result;
}
if(rc > 0) {
size_t readdir_len = (size_t) rc;
sshp->readdir_filename[readdir_len] = '\0';
if(data->set.list_only) {
result = Curl_client_write(data, CLIENTWRITE_BODY,
sshp->readdir_filename,
readdir_len);
if(!result)
result = Curl_client_write(data, CLIENTWRITE_BODY, "\n", 1);
if(result)
return result;
}
else {
result = curlx_dyn_add(&sshp->readdir, sshp->readdir_longentry);
if(!result) {
if((sshp->readdir_attrs.flags & LIBSSH2_SFTP_ATTR_PERMISSIONS) &&
((sshp->readdir_attrs.permissions & LIBSSH2_SFTP_S_IFMT) ==
LIBSSH2_SFTP_S_IFLNK)) {
result = curlx_dyn_addf(&sshp->readdir_link, "%s%s", sshp->path,
sshp->readdir_filename);
myssh_state(data, sshc, SSH_SFTP_READDIR_LINK);
}
else {
myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM);
}
}
return result;
}
}
else if(!rc) {
myssh_state(data, sshc, SSH_SFTP_READDIR_DONE);
}
else {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
result = sftperr ? sftp_libssh2_error_to_CURLE(sftperr) : CURLE_SSH;
failf(data, "Could not open remote file for reading: %s :: %d",
sftp_libssh2_strerror(sftperr),
libssh2_session_last_errno(sshc->ssh_session));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
}
return result;
}
static CURLcode ssh_state_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
CURLcode result;
sshc->secondCreateDirs = 0;
sshc->nextstate = SSH_NO_STATE;
libssh2_session_set_blocking(sshc->ssh_session, 0);
result = ssh_force_knownhost_key_type(data, sshc);
if(result)
myssh_state(data, sshc, SSH_SESSION_FREE);
else
myssh_state(data, sshc, SSH_S_STARTUP);
return result;
}
static CURLcode ssh_state_startup(struct Curl_easy *data,
struct ssh_conn *sshc)
{
struct connectdata *conn = data->conn;
int rc = libssh2_session_handshake(sshc->ssh_session,
conn->sock[FIRSTSOCKET]);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session, &err_msg, NULL, 0);
failf(data, "Failure establishing ssh session: %d, %s", rc, err_msg);
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_FAILED_INIT;
}
myssh_state(data, sshc, SSH_HOSTKEY);
return CURLE_OK;
}
static CURLcode ssh_state_hostkey(struct Curl_easy *data,
struct ssh_conn *sshc)
{
CURLcode result = ssh_check_fingerprint(data, sshc);
if(!result)
myssh_state(data, sshc, SSH_AUTHLIST);
return result;
}
static CURLcode ssh_state_authlist(struct Curl_easy *data,
struct ssh_conn *sshc)
{
struct connectdata *conn = data->conn;
sshc->authlist = libssh2_userauth_list(sshc->ssh_session,
conn->user,
curlx_uztoui(strlen(conn->user)));
if(!sshc->authlist) {
int rc;
if(libssh2_userauth_authenticated(sshc->ssh_session)) {
sshc->authed = TRUE;
infof(data, "SSH user accepted with no authentication");
myssh_state(data, sshc, SSH_AUTH_DONE);
return CURLE_OK;
}
rc = libssh2_session_last_errno(sshc->ssh_session);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
myssh_state(data, sshc, SSH_SESSION_FREE);
return libssh2_session_error_to_CURLE(rc);
}
infof(data, "SSH authentication methods available: %s",
sshc->authlist);
myssh_state(data, sshc, SSH_AUTH_PKEY_INIT);
return CURLE_OK;
}
static CURLcode ssh_state_auth_pkey(struct Curl_easy *data,
struct ssh_conn *sshc)
{
struct connectdata *conn = data->conn;
int rc =
libssh2_userauth_publickey_fromfile_ex(sshc->ssh_session,
conn->user,
curlx_uztoui(
strlen(conn->user)),
sshc->rsa_pub,
sshc->rsa, sshc->passphrase);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
Curl_safefree(sshc->rsa_pub);
Curl_safefree(sshc->rsa);
if(rc == 0) {
sshc->authed = TRUE;
infof(data, "Initialized SSH public key authentication");
myssh_state(data, sshc, SSH_AUTH_DONE);
}
else {
char *err_msg = NULL;
char unknown[] = "Reason unknown (-1)";
if(rc == -1) {
err_msg = unknown;
}
else {
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
}
infof(data, "SSH public key authentication failed: %s", err_msg);
myssh_state(data, sshc, SSH_AUTH_PASS_INIT);
}
return CURLE_OK;
}
static CURLcode ssh_state_auth_pass_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) &&
(strstr(sshc->authlist, "password") != NULL)) {
myssh_state(data, sshc, SSH_AUTH_PASS);
}
else {
myssh_state(data, sshc, SSH_AUTH_HOST_INIT);
}
return CURLE_OK;
}
static CURLcode ssh_state_auth_pass(struct Curl_easy *data,
struct ssh_conn *sshc)
{
struct connectdata *conn = data->conn;
int rc =
libssh2_userauth_password_ex(sshc->ssh_session, conn->user,
curlx_uztoui(strlen(conn->user)),
conn->passwd,
curlx_uztoui(strlen(conn->passwd)),
NULL);
if(rc == LIBSSH2_ERROR_EAGAIN) {
return CURLE_AGAIN;
}
if(rc == 0) {
sshc->authed = TRUE;
infof(data, "Initialized password authentication");
myssh_state(data, sshc, SSH_AUTH_DONE);
}
else {
myssh_state(data, sshc, SSH_AUTH_HOST_INIT);
}
return CURLE_OK;
}
static CURLcode ssh_state_auth_host_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) &&
(strstr(sshc->authlist, "hostbased") != NULL)) {
myssh_state(data, sshc, SSH_AUTH_HOST);
}
else {
myssh_state(data, sshc, SSH_AUTH_AGENT_INIT);
}
return CURLE_OK;
}
static CURLcode ssh_state_auth_agent_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = 0;
if((data->set.ssh_auth_types & CURLSSH_AUTH_AGENT)
&& (strstr(sshc->authlist, "publickey") != NULL)) {
if(!sshc->ssh_agent) {
sshc->ssh_agent = libssh2_agent_init(sshc->ssh_session);
if(!sshc->ssh_agent) {
infof(data, "Could not create agent object");
myssh_state(data, sshc, SSH_AUTH_KEY_INIT);
return CURLE_OK;
}
}
rc = libssh2_agent_connect(sshc->ssh_agent);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc < 0) {
infof(data, "Failure connecting to agent");
myssh_state(data, sshc, SSH_AUTH_KEY_INIT);
}
else {
myssh_state(data, sshc, SSH_AUTH_AGENT_LIST);
}
}
else
myssh_state(data, sshc, SSH_AUTH_KEY_INIT);
return CURLE_OK;
}
static CURLcode ssh_state_auth_agent_list(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = libssh2_agent_list_identities(sshc->ssh_agent);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc < 0) {
infof(data, "Failure requesting identities to agent");
myssh_state(data, sshc, SSH_AUTH_KEY_INIT);
}
else {
myssh_state(data, sshc, SSH_AUTH_AGENT);
sshc->sshagent_prev_identity = NULL;
}
return CURLE_OK;
}
static CURLcode ssh_state_auth_agent(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = libssh2_agent_get_identity(sshc->ssh_agent,
&sshc->sshagent_identity,
sshc->sshagent_prev_identity);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc == 0) {
struct connectdata *conn = data->conn;
rc = libssh2_agent_userauth(sshc->ssh_agent, conn->user,
sshc->sshagent_identity);
if(rc < 0) {
if(rc != LIBSSH2_ERROR_EAGAIN) {
sshc->sshagent_prev_identity = sshc->sshagent_identity;
return CURLE_OK;
}
return CURLE_AGAIN;
}
}
if(rc < 0)
infof(data, "Failure requesting identities to agent");
else if(rc == 1)
infof(data, "No identity would match");
if(rc == LIBSSH2_ERROR_NONE) {
sshc->authed = TRUE;
infof(data, "Agent based authentication successful");
myssh_state(data, sshc, SSH_AUTH_DONE);
}
else {
myssh_state(data, sshc, SSH_AUTH_KEY_INIT);
}
return CURLE_OK;
}
static CURLcode ssh_state_auth_key_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD)
&& (strstr(sshc->authlist, "keyboard-interactive") != NULL)) {
myssh_state(data, sshc, SSH_AUTH_KEY);
}
else {
myssh_state(data, sshc, SSH_AUTH_DONE);
}
return CURLE_OK;
}
static CURLcode ssh_state_auth_key(struct Curl_easy *data,
struct ssh_conn *sshc)
{
struct connectdata *conn = data->conn;
int rc =
libssh2_userauth_keyboard_interactive_ex(sshc->ssh_session,
conn->user,
curlx_uztoui(
strlen(conn->user)),
&kbd_callback);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc == 0) {
sshc->authed = TRUE;
infof(data, "Initialized keyboard interactive authentication");
myssh_state(data, sshc, SSH_AUTH_DONE);
return CURLE_OK;
}
return CURLE_LOGIN_DENIED;
}
static CURLcode ssh_state_auth_done(struct Curl_easy *data,
struct ssh_conn *sshc)
{
struct connectdata *conn = data->conn;
if(!sshc->authed) {
failf(data, "Authentication failure");
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_LOGIN_DENIED;
}
infof(data, "Authentication complete");
Curl_pgrsTime(data, TIMER_APPCONNECT);
data->conn->recv_idx = FIRSTSOCKET;
conn->send_idx = -1;
if(conn->handler->protocol == CURLPROTO_SFTP) {
myssh_state(data, sshc, SSH_SFTP_INIT);
return CURLE_OK;
}
infof(data, "SSH CONNECT phase done");
myssh_state(data, sshc, SSH_STOP);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
sshc->sftp_session = libssh2_sftp_init(sshc->ssh_session);
if(!sshc->sftp_session) {
char *err_msg = NULL;
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
failf(data, "Failure initializing sftp session: %s", err_msg);
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_FAILED_INIT;
}
myssh_state(data, sshc, SSH_SFTP_REALPATH);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_realpath(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
int rc = libssh2_sftp_symlink_ex(sshc->sftp_session,
".", curlx_uztoui(strlen(".")),
sshp->readdir_filename, CURL_PATH_MAX,
LIBSSH2_SFTP_REALPATH);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
myssh_state(data, sshc, SSH_STOP);
if(rc > 0) {
free(sshc->homedir);
sshc->homedir = strdup(sshp->readdir_filename);
if(!sshc->homedir)
return CURLE_OUT_OF_MEMORY;
free(data->state.most_recent_ftp_entrypath);
data->state.most_recent_ftp_entrypath = strdup(sshc->homedir);
if(!data->state.most_recent_ftp_entrypath)
return CURLE_OUT_OF_MEMORY;
}
else {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
CURLcode result;
if(sftperr)
result = sftp_libssh2_error_to_CURLE(sftperr);
else
result = CURLE_SSH;
DEBUGF(infof(data, "error = %lu makes libcurl = %d",
sftperr, (int)result));
return result;
}
DEBUGF(infof(data, "SSH CONNECT phase done"));
return CURLE_OK;
}
static CURLcode ssh_state_sftp_quote_init(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
CURLcode result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
if(result) {
myssh_state(data, sshc, SSH_STOP);
return result;
}
if(data->set.quote) {
infof(data, "Sending quote commands");
sshc->quote_item = data->set.quote;
myssh_state(data, sshc, SSH_SFTP_QUOTE);
}
else {
myssh_state(data, sshc, SSH_SFTP_GETINFO);
}
return CURLE_OK;
}
static CURLcode ssh_state_sftp_postquote_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if(data->set.postquote) {
infof(data, "Sending quote commands");
sshc->quote_item = data->set.postquote;
myssh_state(data, sshc, SSH_SFTP_QUOTE);
}
else {
myssh_state(data, sshc, SSH_STOP);
}
return CURLE_OK;
}
static CURLcode ssh_state_sftp_quote(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
CURLcode result = sftp_quote(data, sshc, sshp);
if(result) {
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
}
return result;
}
static CURLcode ssh_state_sftp_next_quote(struct Curl_easy *data,
struct ssh_conn *sshc)
{
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
sshc->quote_item = sshc->quote_item->next;
if(sshc->quote_item) {
myssh_state(data, sshc, SSH_SFTP_QUOTE);
}
else {
if(sshc->nextstate != SSH_NO_STATE) {
myssh_state(data, sshc, sshc->nextstate);
sshc->nextstate = SSH_NO_STATE;
}
else {
myssh_state(data, sshc, SSH_SFTP_GETINFO);
}
}
return CURLE_OK;
}
static CURLcode ssh_state_sftp_quote_stat(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp,
bool *blockp)
{
CURLcode result = sftp_quote_stat(data, sshc, sshp, blockp);
if(result) {
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
}
return result;
}
static CURLcode ssh_state_sftp_quote_setstat(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
int rc =
libssh2_sftp_stat_ex(sshc->sftp_session, sshc->quote_path2,
curlx_uztoui(strlen(sshc->quote_path2)),
LIBSSH2_SFTP_SETSTAT,
&sshp->quote_attrs);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc && !sshc->acceptfail) {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Attempt to set SFTP stats failed: %s",
sftp_libssh2_strerror(sftperr));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return CURLE_QUOTE_ERROR;
}
myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_quote_symlink(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc =
libssh2_sftp_symlink_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)),
sshc->quote_path2,
curlx_uztoui(strlen(sshc->quote_path2)),
LIBSSH2_SFTP_SYMLINK);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc && !sshc->acceptfail) {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "symlink command failed: %s",
sftp_libssh2_strerror(sftperr));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return CURLE_QUOTE_ERROR;
}
myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_quote_mkdir(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)),
(long)data->set.new_directory_perms);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc && !sshc->acceptfail) {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
failf(data, "mkdir command failed: %s",
sftp_libssh2_strerror(sftperr));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return CURLE_QUOTE_ERROR;
}
myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_quote_rename(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc =
libssh2_sftp_rename_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)),
sshc->quote_path2,
curlx_uztoui(strlen(sshc->quote_path2)),
LIBSSH2_SFTP_RENAME_OVERWRITE |
LIBSSH2_SFTP_RENAME_ATOMIC |
LIBSSH2_SFTP_RENAME_NATIVE);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc && !sshc->acceptfail) {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "rename command failed: %s",
sftp_libssh2_strerror(sftperr));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return CURLE_QUOTE_ERROR;
}
myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_quote_rmdir(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = libssh2_sftp_rmdir_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)));
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc && !sshc->acceptfail) {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
failf(data, "rmdir command failed: %s",
sftp_libssh2_strerror(sftperr));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return CURLE_QUOTE_ERROR;
}
myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_quote_unlink(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = libssh2_sftp_unlink_ex(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)));
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc && !sshc->acceptfail) {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
failf(data, "rm command failed: %s", sftp_libssh2_strerror(sftperr));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return CURLE_QUOTE_ERROR;
}
myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_quote_statvfs(struct Curl_easy *data,
struct ssh_conn *sshc)
{
LIBSSH2_SFTP_STATVFS statvfs;
int rc = libssh2_sftp_statvfs(sshc->sftp_session, sshc->quote_path1,
curlx_uztoui(strlen(sshc->quote_path1)),
&statvfs);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc && !sshc->acceptfail) {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
Curl_safefree(sshc->quote_path1);
failf(data, "statvfs command failed: %s",
sftp_libssh2_strerror(sftperr));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return CURLE_QUOTE_ERROR;
}
else if(rc == 0) {
#ifdef _MSC_VER
#define CURL_LIBSSH2_VFS_SIZE_MASK "I64u"
#else
#define CURL_LIBSSH2_VFS_SIZE_MASK "llu"
#endif
CURLcode result;
char *tmp = curl_maprintf("statvfs:\n"
"f_bsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_frsize: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_blocks: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_bfree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_bavail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_files: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_ffree: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_favail: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_fsid: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_flag: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n"
"f_namemax: %" CURL_LIBSSH2_VFS_SIZE_MASK "\n",
statvfs.f_bsize, statvfs.f_frsize,
statvfs.f_blocks, statvfs.f_bfree,
statvfs.f_bavail, statvfs.f_files,
statvfs.f_ffree, statvfs.f_favail,
statvfs.f_fsid, statvfs.f_flag,
statvfs.f_namemax);
if(!tmp) {
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return CURLE_OUT_OF_MEMORY;
}
result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp));
free(tmp);
if(result) {
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return result;
}
}
myssh_state(data, sshc, SSH_SFTP_NEXT_QUOTE);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_create_dirs_mkdir(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
int rc = libssh2_sftp_mkdir_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
(long)data->set.new_directory_perms);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
*sshc->slash_pos = '/';
++sshc->slash_pos;
if(rc < 0) {
unsigned long sftperr = libssh2_sftp_last_error(sshc->sftp_session);
if((sftperr != LIBSSH2_FX_FILE_ALREADY_EXISTS) &&
(sftperr != LIBSSH2_FX_FAILURE) &&
(sftperr != LIBSSH2_FX_PERMISSION_DENIED)) {
myssh_state(data, sshc, SSH_SFTP_CLOSE);
return sftp_libssh2_error_to_CURLE(sftperr);
}
}
myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_readdir_init(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
Curl_pgrsSetDownloadSize(data, -1);
if(data->req.no_body) {
myssh_state(data, sshc, SSH_STOP);
return CURLE_OK;
}
sshc->sftp_handle =
libssh2_sftp_open_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
0, 0, LIBSSH2_SFTP_OPENDIR);
if(!sshc->sftp_handle) {
unsigned long sftperr;
if(libssh2_session_last_errno(sshc->ssh_session) == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
failf(data, "Could not open directory for reading: %s",
sftp_libssh2_strerror(sftperr));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
return sftp_libssh2_error_to_CURLE(sftperr);
}
myssh_state(data, sshc, SSH_SFTP_READDIR);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_readdir_link(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
CURLcode result;
int rc =
libssh2_sftp_symlink_ex(sshc->sftp_session,
curlx_dyn_ptr(&sshp->readdir_link),
(unsigned int)
curlx_dyn_len(&sshp->readdir_link),
sshp->readdir_filename,
CURL_PATH_MAX, LIBSSH2_SFTP_READLINK);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
curlx_dyn_free(&sshp->readdir_link);
if(rc < 0)
return CURLE_OUT_OF_MEMORY;
result = curlx_dyn_addf(&sshp->readdir, " -> %s", sshp->readdir_filename);
if(result)
myssh_state(data, sshc, SSH_SFTP_CLOSE);
else
myssh_state(data, sshc, SSH_SFTP_READDIR_BOTTOM);
return result;
}
static CURLcode ssh_state_scp_download_init(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
curl_off_t bytecount;
libssh2_struct_stat sb;
memset(&sb, 0, sizeof(libssh2_struct_stat));
sshc->ssh_channel = libssh2_scp_recv2(sshc->ssh_session, sshp->path, &sb);
if(!sshc->ssh_channel) {
int ssh_err;
char *err_msg = NULL;
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0));
failf(data, "%s", err_msg);
myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE);
return libssh2_session_error_to_CURLE(ssh_err);
}
bytecount = (curl_off_t)sb.st_size;
data->req.maxdownload = (curl_off_t)sb.st_size;
Curl_xfer_setup_recv(data, FIRSTSOCKET, bytecount);
data->conn->send_idx = 0;
myssh_state(data, sshc, SSH_STOP);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_close(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
int rc = 0;
if(sshc->sftp_handle) {
rc = libssh2_sftp_close(sshc->sftp_handle);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg);
}
sshc->sftp_handle = NULL;
}
Curl_safefree(sshp->path);
DEBUGF(infof(data, "SFTP DONE done"));
if(sshc->nextstate != SSH_NO_STATE &&
sshc->nextstate != SSH_SFTP_CLOSE) {
myssh_state(data, sshc, sshc->nextstate);
sshc->nextstate = SSH_SFTP_CLOSE;
}
else
myssh_state(data, sshc, SSH_STOP);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_shutdown(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = 0;
if(sshc->sftp_handle) {
rc = libssh2_sftp_close(sshc->sftp_handle);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session, &err_msg,
NULL, 0);
infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg);
}
sshc->sftp_handle = NULL;
}
if(sshc->sftp_session) {
rc = libssh2_sftp_shutdown(sshc->sftp_session);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc < 0) {
infof(data, "Failed to stop libssh2 sftp subsystem");
}
sshc->sftp_session = NULL;
}
Curl_safefree(sshc->homedir);
myssh_state(data, sshc, SSH_SESSION_DISCONNECT);
return CURLE_OK;
}
static CURLcode ssh_state_sftp_download_init(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
sshc->sftp_handle =
libssh2_sftp_open_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
LIBSSH2_FXF_READ, (long)data->set.new_file_perms,
LIBSSH2_SFTP_OPENFILE);
if(!sshc->sftp_handle) {
unsigned long sftperr;
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN) {
return CURLE_AGAIN;
}
sftperr = libssh2_sftp_last_error(sshc->sftp_session);
failf(data, "Could not open remote file for reading: %s",
sftp_libssh2_strerror(sftperr));
myssh_state(data, sshc, SSH_SFTP_CLOSE);
return sftp_libssh2_error_to_CURLE(sftperr);
}
myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_STAT);
return CURLE_OK;
}
static CURLcode ssh_state_scp_upload_init(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
sshc->ssh_channel =
libssh2_scp_send64(sshc->ssh_session, sshp->path,
(int)data->set.new_file_perms,
(libssh2_int64_t)data->state.infilesize, 0, 0);
if(!sshc->ssh_channel) {
int ssh_err;
char *err_msg = NULL;
CURLcode result;
if(libssh2_session_last_errno(sshc->ssh_session) ==
LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
ssh_err = (int)(libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0));
failf(data, "%s", err_msg);
myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE);
result = libssh2_session_error_to_CURLE(ssh_err);
if(result == CURLE_SSH ||
result == CURLE_REMOTE_FILE_NOT_FOUND)
result = CURLE_UPLOAD_FAILED;
return result;
}
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->state.infilesize);
Curl_xfer_setup_send(data, FIRSTSOCKET);
data->conn->recv_idx = FIRSTSOCKET;
sshc->orig_waitfor = data->req.keepon;
myssh_state(data, sshc, SSH_STOP);
return CURLE_OK;
}
static CURLcode ssh_state_session_disconnect(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = 0;
if(sshc->ssh_channel) {
rc = libssh2_channel_free(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to free libssh2 scp subsystem: %d %s",
rc, err_msg);
}
sshc->ssh_channel = NULL;
}
if(sshc->ssh_session) {
rc = libssh2_session_disconnect(sshc->ssh_session, "Shutdown");
if(rc == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to disconnect libssh2 session: %d %s",
rc, err_msg);
}
}
Curl_safefree(sshc->homedir);
myssh_state(data, sshc, SSH_SESSION_FREE);
return CURLE_OK;
}
static CURLcode ssh_statemachine(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp,
bool *block)
{
CURLcode result = CURLE_OK;
struct connectdata *conn = data->conn;
*block = 0;
do {
switch(sshc->state) {
case SSH_INIT:
result = ssh_state_init(data, sshc);
if(result)
break;
FALLTHROUGH();
case SSH_S_STARTUP:
result = ssh_state_startup(data, sshc);
if(result)
break;
FALLTHROUGH();
case SSH_HOSTKEY:
result = ssh_state_hostkey(data, sshc);
break;
case SSH_AUTHLIST:
result = ssh_state_authlist(data, sshc);
break;
case SSH_AUTH_PKEY_INIT:
result = ssh_state_pkey_init(data, sshc);
break;
case SSH_AUTH_PKEY:
result = ssh_state_auth_pkey(data, sshc);
break;
case SSH_AUTH_PASS_INIT:
result = ssh_state_auth_pass_init(data, sshc);
break;
case SSH_AUTH_PASS:
result = ssh_state_auth_pass(data, sshc);
break;
case SSH_AUTH_HOST_INIT:
result = ssh_state_auth_host_init(data, sshc);
break;
case SSH_AUTH_HOST:
myssh_state(data, sshc, SSH_AUTH_AGENT_INIT);
break;
case SSH_AUTH_AGENT_INIT:
result = ssh_state_auth_agent_init(data, sshc);
break;
case SSH_AUTH_AGENT_LIST:
result = ssh_state_auth_agent_list(data, sshc);
break;
case SSH_AUTH_AGENT:
result = ssh_state_auth_agent(data, sshc);
break;
case SSH_AUTH_KEY_INIT:
result = ssh_state_auth_key_init(data, sshc);
break;
case SSH_AUTH_KEY:
result = ssh_state_auth_key(data, sshc);
break;
case SSH_AUTH_DONE:
result = ssh_state_auth_done(data, sshc);
break;
case SSH_SFTP_INIT:
result = ssh_state_sftp_init(data, sshc);
break;
case SSH_SFTP_REALPATH:
result = sshp ? ssh_state_sftp_realpath(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_QUOTE_INIT:
result = sshp ? ssh_state_sftp_quote_init(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_POSTQUOTE_INIT:
result = ssh_state_sftp_postquote_init(data, sshc);
break;
case SSH_SFTP_QUOTE:
result = sshp ? ssh_state_sftp_quote(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_NEXT_QUOTE:
result = ssh_state_sftp_next_quote(data, sshc);
break;
case SSH_SFTP_QUOTE_STAT:
result = sshp ? ssh_state_sftp_quote_stat(data, sshc, sshp, block) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_QUOTE_SETSTAT:
result = sshp ? ssh_state_sftp_quote_setstat(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_QUOTE_SYMLINK:
result = ssh_state_sftp_quote_symlink(data, sshc);
break;
case SSH_SFTP_QUOTE_MKDIR:
result = ssh_state_sftp_quote_mkdir(data, sshc);
break;
case SSH_SFTP_QUOTE_RENAME:
result = ssh_state_sftp_quote_rename(data, sshc);
break;
case SSH_SFTP_QUOTE_RMDIR:
result = ssh_state_sftp_quote_rmdir(data, sshc);
break;
case SSH_SFTP_QUOTE_UNLINK:
result = ssh_state_sftp_quote_unlink(data, sshc);
break;
case SSH_SFTP_QUOTE_STATVFS:
result = ssh_state_sftp_quote_statvfs(data, sshc);
break;
case SSH_SFTP_GETINFO:
if(data->set.get_filetime) {
myssh_state(data, sshc, SSH_SFTP_FILETIME);
}
else {
myssh_state(data, sshc, SSH_SFTP_TRANS_INIT);
}
break;
case SSH_SFTP_FILETIME:
{
LIBSSH2_SFTP_ATTRIBUTES attrs;
int rc;
if(!sshp) {
result = CURLE_FAILED_INIT;
break;
}
rc = libssh2_sftp_stat_ex(sshc->sftp_session, sshp->path,
curlx_uztoui(strlen(sshp->path)),
LIBSSH2_SFTP_STAT, &attrs);
if(rc == LIBSSH2_ERROR_EAGAIN) {
result = CURLE_AGAIN;
break;
}
if(rc == 0) {
data->info.filetime = (time_t)attrs.mtime;
}
myssh_state(data, sshc, SSH_SFTP_TRANS_INIT);
break;
}
case SSH_SFTP_TRANS_INIT:
if(data->state.upload)
myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT);
else if(sshp) {
if(sshp->path[strlen(sshp->path)-1] == '/')
myssh_state(data, sshc, SSH_SFTP_READDIR_INIT);
else
myssh_state(data, sshc, SSH_SFTP_DOWNLOAD_INIT);
}
else
result = CURLE_FAILED_INIT;
break;
case SSH_SFTP_UPLOAD_INIT:
result = sshp ? sftp_upload_init(data, sshc, sshp, block) :
CURLE_FAILED_INIT;
if(result) {
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
}
break;
case SSH_SFTP_CREATE_DIRS_INIT:
if(!sshp)
result = CURLE_FAILED_INIT;
else if(strlen(sshp->path) > 1) {
sshc->slash_pos = sshp->path + 1;
myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS);
}
else {
myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT);
}
break;
case SSH_SFTP_CREATE_DIRS:
if(!sshp) {
result = CURLE_FAILED_INIT;
break;
}
sshc->slash_pos = strchr(sshc->slash_pos, '/');
if(sshc->slash_pos) {
*sshc->slash_pos = 0;
infof(data, "Creating directory '%s'", sshp->path);
myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR);
break;
}
myssh_state(data, sshc, SSH_SFTP_UPLOAD_INIT);
break;
case SSH_SFTP_CREATE_DIRS_MKDIR:
result = sshp ? ssh_state_sftp_create_dirs_mkdir(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_READDIR_INIT:
result = sshp ? ssh_state_sftp_readdir_init(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_READDIR:
result = sshp ? sftp_readdir(data, sshc, sshp, block) :
CURLE_FAILED_INIT;
if(result) {
myssh_state(data, sshc, SSH_SFTP_CLOSE);
}
break;
case SSH_SFTP_READDIR_LINK:
result = sshp ? ssh_state_sftp_readdir_link(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_READDIR_BOTTOM:
if(!sshp) {
result = CURLE_FAILED_INIT;
break;
}
result = curlx_dyn_addn(&sshp->readdir, "\n", 1);
if(!result)
result = Curl_client_write(data, CLIENTWRITE_BODY,
curlx_dyn_ptr(&sshp->readdir),
curlx_dyn_len(&sshp->readdir));
if(result) {
curlx_dyn_free(&sshp->readdir);
myssh_state(data, sshc, SSH_STOP);
}
else {
curlx_dyn_reset(&sshp->readdir);
myssh_state(data, sshc, SSH_SFTP_READDIR);
}
break;
case SSH_SFTP_READDIR_DONE:
if(libssh2_sftp_closedir(sshc->sftp_handle) == LIBSSH2_ERROR_EAGAIN)
result = CURLE_AGAIN;
else {
sshc->sftp_handle = NULL;
Curl_xfer_setup_nop(data);
myssh_state(data, sshc, SSH_STOP);
}
break;
case SSH_SFTP_DOWNLOAD_INIT:
result = sshp ? ssh_state_sftp_download_init(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_DOWNLOAD_STAT:
result = sshp ? sftp_download_stat(data, sshc, sshp, block) :
CURLE_FAILED_INIT;
if(result) {
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
}
break;
case SSH_SFTP_CLOSE:
result = sshp ? ssh_state_sftp_close(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_SHUTDOWN:
result = ssh_state_sftp_shutdown(data, sshc);
break;
case SSH_SCP_TRANS_INIT:
result = sshp ? Curl_getworkingpath(data, sshc->homedir, &sshp->path) :
CURLE_FAILED_INIT;
if(result) {
myssh_state(data, sshc, SSH_STOP);
break;
}
if(data->state.upload) {
if(data->state.infilesize < 0) {
failf(data, "SCP requires a known file size for upload");
result = CURLE_UPLOAD_FAILED;
myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE);
break;
}
myssh_state(data, sshc, SSH_SCP_UPLOAD_INIT);
}
else {
myssh_state(data, sshc, SSH_SCP_DOWNLOAD_INIT);
}
break;
case SSH_SCP_UPLOAD_INIT:
result = sshp ? ssh_state_scp_upload_init(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SCP_DOWNLOAD_INIT:
result = sshp ? ssh_state_scp_download_init(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SCP_DONE:
if(data->state.upload)
myssh_state(data, sshc, SSH_SCP_SEND_EOF);
else
myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE);
break;
case SSH_SCP_SEND_EOF:
if(sshc->ssh_channel) {
int rc = libssh2_channel_send_eof(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN) {
result = CURLE_AGAIN;
break;
}
if(rc) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to send libssh2 channel EOF: %d %s",
rc, err_msg);
}
}
myssh_state(data, sshc, SSH_SCP_WAIT_EOF);
break;
case SSH_SCP_WAIT_EOF:
if(sshc->ssh_channel) {
int rc = libssh2_channel_wait_eof(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN) {
result = CURLE_AGAIN;
break;
}
if(rc) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to get channel EOF: %d %s", rc, err_msg);
}
}
myssh_state(data, sshc, SSH_SCP_WAIT_CLOSE);
break;
case SSH_SCP_WAIT_CLOSE:
if(sshc->ssh_channel) {
int rc = libssh2_channel_wait_closed(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN) {
result = CURLE_AGAIN;
break;
}
if(rc) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Channel failed to close: %d %s", rc, err_msg);
}
}
myssh_state(data, sshc, SSH_SCP_CHANNEL_FREE);
break;
case SSH_SCP_CHANNEL_FREE:
if(sshc->ssh_channel) {
int rc = libssh2_channel_free(sshc->ssh_channel);
if(rc == LIBSSH2_ERROR_EAGAIN) {
result = CURLE_AGAIN;
break;
}
if(rc < 0) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to free libssh2 scp subsystem: %d %s",
rc, err_msg);
}
sshc->ssh_channel = NULL;
}
DEBUGF(infof(data, "SCP DONE phase complete"));
myssh_state(data, sshc, SSH_STOP);
break;
case SSH_SESSION_DISCONNECT:
result = ssh_state_session_disconnect(data, sshc);
break;
case SSH_SESSION_FREE:
result = sshc_cleanup(sshc, data, FALSE);
if(result)
break;
memset(sshc, 0, sizeof(struct ssh_conn));
connclose(conn, "SSH session free");
sshc->state = SSH_SESSION_FREE;
myssh_state(data, sshc, SSH_STOP);
break;
case SSH_QUIT:
default:
myssh_state(data, sshc, SSH_STOP);
break;
}
} while(!result && (sshc->state != SSH_STOP) && !*block);
if(result == CURLE_AGAIN) {
*block = TRUE;
result = CURLE_OK;
}
return result;
}
static CURLcode ssh_pollset(struct Curl_easy *data,
struct easy_pollset *ps)
{
int flags = 0;
struct connectdata *conn = data->conn;
if(conn->waitfor & KEEP_RECV)
flags |= CURL_POLL_IN;
if(conn->waitfor & KEEP_SEND)
flags |= CURL_POLL_OUT;
return flags ?
Curl_pollset_change(data, ps, conn->sock[FIRSTSOCKET], flags, 0) :
CURLE_OK;
}
static void ssh_block2waitfor(struct Curl_easy *data,
struct ssh_conn *sshc,
bool block)
{
struct connectdata *conn = data->conn;
int dir = 0;
if(block) {
dir = libssh2_session_block_directions(sshc->ssh_session);
if(dir) {
conn->waitfor = ((dir&LIBSSH2_SESSION_BLOCK_INBOUND) ? KEEP_RECV : 0) |
((dir&LIBSSH2_SESSION_BLOCK_OUTBOUND) ? KEEP_SEND : 0);
}
}
if(!dir)
conn->waitfor = sshc->orig_waitfor;
}
static CURLcode ssh_multi_statemach(struct Curl_easy *data, bool *done)
{
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY);
CURLcode result = CURLE_OK;
bool block;
if(!sshc || !sshp)
return CURLE_FAILED_INIT;
do {
result = ssh_statemachine(data, sshc, sshp, &block);
*done = (sshc->state == SSH_STOP);
} while(!result && !*done && !block);
ssh_block2waitfor(data, sshc, block);
return result;
}
static CURLcode ssh_block_statemach(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp,
bool disconnect)
{
CURLcode result = CURLE_OK;
struct curltime dis = curlx_now();
while((sshc->state != SSH_STOP) && !result) {
bool block;
timediff_t left = 1000;
struct curltime now = curlx_now();
result = ssh_statemachine(data, sshc, sshp, &block);
if(result)
break;
if(!disconnect) {
if(Curl_pgrsUpdate(data))
return CURLE_ABORTED_BY_CALLBACK;
result = Curl_speedcheck(data, now);
if(result)
break;
left = Curl_timeleft(data, NULL, FALSE);
if(left < 0) {
failf(data, "Operation timed out");
return CURLE_OPERATION_TIMEDOUT;
}
}
else if(curlx_timediff(now, dis) > 1000) {
failf(data, "Disconnect timed out");
result = CURLE_OK;
break;
}
if(block) {
int dir = libssh2_session_block_directions(sshc->ssh_session);
curl_socket_t sock = data->conn->sock[FIRSTSOCKET];
curl_socket_t fd_read = CURL_SOCKET_BAD;
curl_socket_t fd_write = CURL_SOCKET_BAD;
if(LIBSSH2_SESSION_BLOCK_INBOUND & dir)
fd_read = sock;
if(LIBSSH2_SESSION_BLOCK_OUTBOUND & dir)
fd_write = sock;
(void)Curl_socket_check(fd_read, CURL_SOCKET_BAD, fd_write,
left > 1000 ? 1000 : left);
}
}
return result;
}
static void myssh_easy_dtor(void *key, size_t klen, void *entry)
{
struct SSHPROTO *sshp = entry;
(void)key;
(void)klen;
Curl_safefree(sshp->path);
curlx_dyn_free(&sshp->readdir);
curlx_dyn_free(&sshp->readdir_link);
free(sshp);
}
static void myssh_conn_dtor(void *key, size_t klen, void *entry)
{
struct ssh_conn *sshc = entry;
(void)key;
(void)klen;
sshc_cleanup(sshc, NULL, TRUE);
free(sshc);
}
static CURLcode ssh_setup_connection(struct Curl_easy *data,
struct connectdata *conn)
{
struct ssh_conn *sshc;
struct SSHPROTO *sshp;
(void)conn;
sshc = calloc(1, sizeof(*sshc));
if(!sshc)
return CURLE_OUT_OF_MEMORY;
if(Curl_conn_meta_set(conn, CURL_META_SSH_CONN, sshc, myssh_conn_dtor))
return CURLE_OUT_OF_MEMORY;
sshp = calloc(1, sizeof(*sshp));
if(!sshp)
return CURLE_OUT_OF_MEMORY;
curlx_dyn_init(&sshp->readdir, CURL_PATH_MAX * 2);
curlx_dyn_init(&sshp->readdir_link, CURL_PATH_MAX);
if(Curl_meta_set(data, CURL_META_SSH_EASY, sshp, myssh_easy_dtor))
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
}
static Curl_recv scp_recv, sftp_recv;
static Curl_send scp_send, sftp_send;
#ifndef CURL_DISABLE_PROXY
static ssize_t ssh_tls_recv(libssh2_socket_t sock, void *buffer,
size_t length, int flags, void **abstract)
{
struct Curl_easy *data = (struct Curl_easy *)*abstract;
int sockindex = Curl_conn_sockindex(data, sock);
size_t nread;
CURLcode result;
struct connectdata *conn = data->conn;
Curl_recv *backup = conn->recv[sockindex];
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
(void)flags;
if(!sshc)
return -1;
conn->recv[sockindex] = sshc->tls_recv;
result = Curl_conn_recv(data, sockindex, buffer, length, &nread);
conn->recv[sockindex] = backup;
if(result == CURLE_AGAIN)
return -EAGAIN;
else if(result)
return -1;
Curl_debug(data, CURLINFO_DATA_IN, (const char *)buffer, (size_t)nread);
return (ssize_t)nread;
}
static ssize_t ssh_tls_send(libssh2_socket_t sock, const void *buffer,
size_t length, int flags, void **abstract)
{
struct Curl_easy *data = (struct Curl_easy *)*abstract;
int sockindex = Curl_conn_sockindex(data, sock);
size_t nwrite;
CURLcode result;
struct connectdata *conn = data->conn;
Curl_send *backup = conn->send[sockindex];
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
(void)flags;
if(!sshc)
return -1;
conn->send[sockindex] = sshc->tls_send;
result = Curl_conn_send(data, sockindex, buffer, length, FALSE, &nwrite);
conn->send[sockindex] = backup;
if(result == CURLE_AGAIN)
return -EAGAIN;
else if(result)
return -1;
Curl_debug(data, CURLINFO_DATA_OUT, (const char *)buffer, nwrite);
return (ssize_t)nwrite;
}
#endif
static CURLcode ssh_connect(struct Curl_easy *data, bool *done)
{
#ifdef CURL_LIBSSH2_DEBUG
curl_socket_t sock;
#endif
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
CURLcode result;
#if LIBSSH2_VERSION_NUM >= 0x010b00
{
const char *crypto_str;
switch(libssh2_crypto_engine()) {
case libssh2_gcrypt:
crypto_str = "libgcrypt";
break;
case libssh2_mbedtls:
crypto_str = "mbedTLS";
break;
case libssh2_openssl:
crypto_str = "openssl compatible";
break;
case libssh2_os400qc3:
crypto_str = "OS400QC3";
break;
case libssh2_wincng:
crypto_str = "WinCNG";
break;
default:
crypto_str = NULL;
break;
}
if(crypto_str)
infof(data, "libssh2 cryptography backend: %s", crypto_str);
}
#endif
if(!sshc)
return CURLE_FAILED_INIT;
connkeep(conn, "SSH default");
infof(data, "User: '%s'", conn->user);
#ifdef CURL_LIBSSH2_DEBUG
infof(data, "Password: %s", conn->passwd);
sock = conn->sock[FIRSTSOCKET];
#endif
sshc->ssh_session = libssh2_session_init_ex(my_libssh2_malloc,
my_libssh2_free,
my_libssh2_realloc, data);
if(!sshc->ssh_session) {
failf(data, "Failure initialising ssh session");
return CURLE_FAILED_INIT;
}
#if LIBSSH2_VERSION_NUM >= 0x010B00
if(data->set.server_response_timeout > 0) {
libssh2_session_set_read_timeout(sshc->ssh_session,
(long)(data->set.server_response_timeout / 1000));
}
#endif
#ifndef CURL_DISABLE_PROXY
if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) {
#if LIBSSH2_VERSION_NUM >= 0x010b01
infof(data, "Uses HTTPS proxy");
#if defined(__clang__) && __clang_major__ >= 16
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-function-type-strict"
#endif
libssh2_session_callback_set2(sshc->ssh_session,
LIBSSH2_CALLBACK_RECV,
(libssh2_cb_generic *)ssh_tls_recv);
libssh2_session_callback_set2(sshc->ssh_session,
LIBSSH2_CALLBACK_SEND,
(libssh2_cb_generic *)ssh_tls_send);
#if defined(__clang__) && __clang_major__ >= 16
#pragma clang diagnostic pop
#endif
#else
union receive {
void *recvp;
ssize_t (*recvptr)(libssh2_socket_t, void *, size_t, int, void **);
};
union transfer {
void *sendp;
ssize_t (*sendptr)(libssh2_socket_t, const void *, size_t, int, void **);
};
union receive sshrecv;
union transfer sshsend;
sshrecv.recvptr = ssh_tls_recv;
sshsend.sendptr = ssh_tls_send;
infof(data, "Uses HTTPS proxy");
libssh2_session_callback_set(sshc->ssh_session,
LIBSSH2_CALLBACK_RECV, sshrecv.recvp);
libssh2_session_callback_set(sshc->ssh_session,
LIBSSH2_CALLBACK_SEND, sshsend.sendp);
#endif
sshc->tls_recv = conn->recv[FIRSTSOCKET];
sshc->tls_send = conn->send[FIRSTSOCKET];
}
#endif
if(conn->handler->protocol & CURLPROTO_SCP) {
conn->recv[FIRSTSOCKET] = scp_recv;
conn->send[FIRSTSOCKET] = scp_send;
}
else {
conn->recv[FIRSTSOCKET] = sftp_recv;
conn->send[FIRSTSOCKET] = sftp_send;
}
if(data->set.ssh_compression &&
libssh2_session_flag(sshc->ssh_session, LIBSSH2_FLAG_COMPRESS, 1) < 0) {
infof(data, "Failed to enable compression for ssh session");
}
if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
int rc;
sshc->kh = libssh2_knownhost_init(sshc->ssh_session);
if(!sshc->kh) {
libssh2_session_free(sshc->ssh_session);
sshc->ssh_session = NULL;
return CURLE_FAILED_INIT;
}
rc = libssh2_knownhost_readfile(sshc->kh,
data->set.str[STRING_SSH_KNOWNHOSTS],
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
if(rc < 0)
infof(data, "Failed to read known hosts from %s",
data->set.str[STRING_SSH_KNOWNHOSTS]);
}
#ifdef CURL_LIBSSH2_DEBUG
libssh2_trace(sshc->ssh_session, ~0);
infof(data, "SSH socket: %d", (int)sock);
#endif
myssh_state(data, sshc, SSH_INIT);
result = ssh_multi_statemach(data, done);
return result;
}
static
CURLcode scp_perform(struct Curl_easy *data,
bool *connected,
bool *dophase_done)
{
struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN);
CURLcode result = CURLE_OK;
DEBUGF(infof(data, "DO phase starts"));
*dophase_done = FALSE;
if(!sshc)
return CURLE_FAILED_INIT;
myssh_state(data, sshc, SSH_SCP_TRANS_INIT);
result = ssh_multi_statemach(data, dophase_done);
*connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET);
if(*dophase_done) {
DEBUGF(infof(data, "DO phase is complete"));
}
return result;
}
static CURLcode scp_doing(struct Curl_easy *data,
bool *dophase_done)
{
CURLcode result;
result = ssh_multi_statemach(data, dophase_done);
if(*dophase_done) {
DEBUGF(infof(data, "DO phase is complete"));
}
return result;
}
static CURLcode ssh_do(struct Curl_easy *data, bool *done)
{
CURLcode result;
bool connected = FALSE;
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
*done = FALSE;
if(!sshc)
return CURLE_FAILED_INIT;
data->req.size = -1;
sshc->secondCreateDirs = 0;
Curl_pgrsSetUploadCounter(data, 0);
Curl_pgrsSetDownloadCounter(data, 0);
Curl_pgrsSetUploadSize(data, -1);
Curl_pgrsSetDownloadSize(data, -1);
if(conn->handler->protocol & CURLPROTO_SCP)
result = scp_perform(data, &connected, done);
else
result = sftp_perform(data, &connected, done);
return result;
}
static CURLcode sshc_cleanup(struct ssh_conn *sshc, struct Curl_easy *data,
bool block)
{
int rc;
if(sshc->kh) {
libssh2_knownhost_free(sshc->kh);
sshc->kh = NULL;
}
if(sshc->ssh_agent) {
rc = libssh2_agent_disconnect(sshc->ssh_agent);
if((rc < 0) && data) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to disconnect from libssh2 agent: %d %s",
rc, err_msg);
}
if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
return CURLE_AGAIN;
libssh2_agent_free(sshc->ssh_agent);
sshc->ssh_agent = NULL;
sshc->sshagent_identity = NULL;
sshc->sshagent_prev_identity = NULL;
}
if(sshc->sftp_handle) {
rc = libssh2_sftp_close(sshc->sftp_handle);
if((rc < 0) && data) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session, &err_msg,
NULL, 0);
infof(data, "Failed to close libssh2 file: %d %s", rc, err_msg);
}
if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
return CURLE_AGAIN;
sshc->sftp_handle = NULL;
}
if(sshc->ssh_channel) {
rc = libssh2_channel_free(sshc->ssh_channel);
if((rc < 0) && data) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to free libssh2 scp subsystem: %d %s",
rc, err_msg);
}
if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
return CURLE_AGAIN;
sshc->ssh_channel = NULL;
}
if(sshc->sftp_session) {
rc = libssh2_sftp_shutdown(sshc->sftp_session);
if((rc < 0) && data) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to stop libssh2 sftp subsystem: %d %s", rc, err_msg);
}
if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
return CURLE_AGAIN;
sshc->sftp_session = NULL;
}
if(sshc->ssh_session) {
rc = libssh2_session_free(sshc->ssh_session);
if((rc < 0) && data) {
char *err_msg = NULL;
(void)libssh2_session_last_error(sshc->ssh_session,
&err_msg, NULL, 0);
infof(data, "Failed to free libssh2 session: %d %s", rc, err_msg);
}
if(!block && (rc == LIBSSH2_ERROR_EAGAIN))
return CURLE_AGAIN;
sshc->ssh_session = NULL;
}
DEBUGASSERT(sshc->ssh_session == NULL);
DEBUGASSERT(sshc->ssh_channel == NULL);
DEBUGASSERT(sshc->sftp_session == NULL);
DEBUGASSERT(sshc->sftp_handle == NULL);
DEBUGASSERT(sshc->kh == NULL);
DEBUGASSERT(sshc->ssh_agent == NULL);
Curl_safefree(sshc->rsa_pub);
Curl_safefree(sshc->rsa);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
Curl_safefree(sshc->homedir);
return CURLE_OK;
}
static CURLcode scp_disconnect(struct Curl_easy *data,
struct connectdata *conn,
bool dead_connection)
{
CURLcode result = CURLE_OK;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY);
(void)dead_connection;
if(sshc && sshc->ssh_session) {
myssh_state(data, sshc, SSH_SESSION_DISCONNECT);
result = ssh_block_statemach(data, sshc, sshp, TRUE);
}
if(sshc)
return sshc_cleanup(sshc, data, TRUE);
return result;
}
static CURLcode ssh_done(struct Curl_easy *data, CURLcode status)
{
struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN);
struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY);
CURLcode result = CURLE_OK;
if(!sshc || !sshp)
return CURLE_FAILED_INIT;
if(!status)
result = ssh_block_statemach(data, sshc, sshp, FALSE);
else
result = status;
if(Curl_pgrsDone(data))
return CURLE_ABORTED_BY_CALLBACK;
data->req.keepon = 0;
return result;
}
static CURLcode scp_done(struct Curl_easy *data, CURLcode status,
bool premature)
{
struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN);
(void)premature;
if(sshc && !status)
myssh_state(data, sshc, SSH_SCP_DONE);
return ssh_done(data, status);
}
static CURLcode scp_send(struct Curl_easy *data, int sockindex,
const void *mem, size_t len, bool eos,
size_t *pnwritten)
{
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
CURLcode result = CURLE_OK;
ssize_t nwritten;
(void)sockindex;
(void)eos;
*pnwritten = 0;
if(!sshc)
return CURLE_FAILED_INIT;
nwritten = (ssize_t) libssh2_channel_write(sshc->ssh_channel, mem, len);
ssh_block2waitfor(data, sshc, (nwritten == LIBSSH2_ERROR_EAGAIN));
if(nwritten == LIBSSH2_ERROR_EAGAIN)
result = CURLE_AGAIN;
else if(nwritten < LIBSSH2_ERROR_NONE)
result = libssh2_session_error_to_CURLE((int)nwritten);
else
*pnwritten = (size_t)nwritten;
return result;
}
static CURLcode scp_recv(struct Curl_easy *data, int sockindex,
char *mem, size_t len, size_t *pnread)
{
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
CURLcode result = CURLE_OK;
ssize_t nread;
(void)sockindex;
*pnread = 0;
if(!sshc)
return CURLE_FAILED_INIT;
nread = (ssize_t) libssh2_channel_read(sshc->ssh_channel, mem, len);
ssh_block2waitfor(data, sshc, (nread == LIBSSH2_ERROR_EAGAIN));
if(nread == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
else if(nread < LIBSSH2_ERROR_NONE)
result = libssh2_session_error_to_CURLE((int)nread);
else
*pnread = (size_t)nread;
return result;
}
static
CURLcode sftp_perform(struct Curl_easy *data,
bool *connected,
bool *dophase_done)
{
struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN);
CURLcode result = CURLE_OK;
DEBUGF(infof(data, "DO phase starts"));
*dophase_done = FALSE;
if(!sshc)
return CURLE_FAILED_INIT;
myssh_state(data, sshc, SSH_SFTP_QUOTE_INIT);
result = ssh_multi_statemach(data, dophase_done);
*connected = Curl_conn_is_connected(data->conn, FIRSTSOCKET);
if(*dophase_done) {
DEBUGF(infof(data, "DO phase is complete"));
}
return result;
}
static CURLcode sftp_doing(struct Curl_easy *data,
bool *dophase_done)
{
CURLcode result = ssh_multi_statemach(data, dophase_done);
if(*dophase_done) {
DEBUGF(infof(data, "DO phase is complete"));
}
return result;
}
static CURLcode sftp_disconnect(struct Curl_easy *data,
struct connectdata *conn, bool dead_connection)
{
CURLcode result = CURLE_OK;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY);
(void)dead_connection;
if(sshc) {
if(sshc->ssh_session) {
DEBUGF(infof(data, "SSH DISCONNECT starts now"));
myssh_state(data, sshc, SSH_SFTP_SHUTDOWN);
result = ssh_block_statemach(data, sshc, sshp, TRUE);
DEBUGF(infof(data, "SSH DISCONNECT is done -> %d", result));
}
sshc_cleanup(sshc, data, TRUE);
}
return result;
}
static CURLcode sftp_done(struct Curl_easy *data, CURLcode status,
bool premature)
{
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
if(!sshc)
return CURLE_FAILED_INIT;
if(!status) {
if(!premature && data->set.postquote && !conn->bits.retry)
sshc->nextstate = SSH_SFTP_POSTQUOTE_INIT;
myssh_state(data, sshc, SSH_SFTP_CLOSE);
}
return ssh_done(data, status);
}
static CURLcode sftp_send(struct Curl_easy *data, int sockindex,
const void *mem, size_t len, bool eos,
size_t *pnwritten)
{
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
ssize_t nwrite;
(void)sockindex;
(void)eos;
*pnwritten = 0;
if(!sshc)
return CURLE_FAILED_INIT;
nwrite = libssh2_sftp_write(sshc->sftp_handle, mem, len);
ssh_block2waitfor(data, sshc, (nwrite == LIBSSH2_ERROR_EAGAIN));
if(nwrite == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
else if(nwrite < LIBSSH2_ERROR_NONE)
return libssh2_session_error_to_CURLE((int)nwrite);
*pnwritten = (size_t)nwrite;
return CURLE_OK;
}
static CURLcode sftp_recv(struct Curl_easy *data, int sockindex,
char *mem, size_t len, size_t *pnread)
{
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
ssize_t nread;
(void)sockindex;
*pnread = 0;
if(!sshc)
return CURLE_FAILED_INIT;
nread = libssh2_sftp_read(sshc->sftp_handle, mem, len);
ssh_block2waitfor(data, sshc, (nread == LIBSSH2_ERROR_EAGAIN));
if(nread == LIBSSH2_ERROR_EAGAIN)
return CURLE_AGAIN;
else if(nread < 0)
return libssh2_session_error_to_CURLE((int)nread);
*pnread = (size_t)nread;
return CURLE_OK;
}
static const char *sftp_libssh2_strerror(unsigned long err)
{
switch(err) {
case LIBSSH2_FX_NO_SUCH_FILE:
return "No such file or directory";
case LIBSSH2_FX_PERMISSION_DENIED:
return "Permission denied";
case LIBSSH2_FX_FAILURE:
return "Operation failed";
case LIBSSH2_FX_BAD_MESSAGE:
return "Bad message from SFTP server";
case LIBSSH2_FX_NO_CONNECTION:
return "Not connected to SFTP server";
case LIBSSH2_FX_CONNECTION_LOST:
return "Connection to SFTP server lost";
case LIBSSH2_FX_OP_UNSUPPORTED:
return "Operation not supported by SFTP server";
case LIBSSH2_FX_INVALID_HANDLE:
return "Invalid handle";
case LIBSSH2_FX_NO_SUCH_PATH:
return "No such file or directory";
case LIBSSH2_FX_FILE_ALREADY_EXISTS:
return "File already exists";
case LIBSSH2_FX_WRITE_PROTECT:
return "File is write protected";
case LIBSSH2_FX_NO_MEDIA:
return "No media";
case LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM:
return "Disk full";
case LIBSSH2_FX_QUOTA_EXCEEDED:
return "User quota exceeded";
case LIBSSH2_FX_UNKNOWN_PRINCIPLE:
return "Unknown principle";
case LIBSSH2_FX_LOCK_CONFlICT:
return "File lock conflict";
case LIBSSH2_FX_DIR_NOT_EMPTY:
return "Directory not empty";
case LIBSSH2_FX_NOT_A_DIRECTORY:
return "Not a directory";
case LIBSSH2_FX_INVALID_FILENAME:
return "Invalid filename";
case LIBSSH2_FX_LINK_LOOP:
return "Link points to itself";
}
return "Unknown error in libssh2";
}
CURLcode Curl_ssh_init(void)
{
if(libssh2_init(0)) {
DEBUGF(curl_mfprintf(stderr, "Error: libssh2_init failed\n"));
return CURLE_FAILED_INIT;
}
return CURLE_OK;
}
void Curl_ssh_cleanup(void)
{
(void)libssh2_exit();
}
void Curl_ssh_version(char *buffer, size_t buflen)
{
(void)curl_msnprintf(buffer, buflen, "libssh2/%s", libssh2_version(0));
}
static void ssh_attach(struct Curl_easy *data, struct connectdata *conn)
{
DEBUGASSERT(data);
DEBUGASSERT(conn);
if(conn->handler->protocol & PROTO_FAMILY_SSH) {
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
if(sshc && sshc->ssh_session) {
void **abstract = libssh2_session_abstract(sshc->ssh_session);
*abstract = data;
}
}
}
#endif