#include "../curl_setup.h"
#ifdef USE_LIBSSH
#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 "../curlx/strparse.h"
#include "../multiif.h"
#include "../select.h"
#include "../curlx/warnless.h"
#include "curl_path.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include "../curl_memory.h"
#include "../memdebug.h"
#ifndef SSH_STRING_FREE_CHAR
#define SSH_STRING_FREE_CHAR(x) \
do { \
if(x) { \
ssh_string_free_char(x); \
x = NULL; \
} \
} while(0)
#endif
#ifndef SSH_S_IFMT
#define SSH_S_IFMT 00170000
#endif
#ifndef SSH_S_IFLNK
#define SSH_S_IFLNK 0120000
#endif
static CURLcode myssh_connect(struct Curl_easy *data, bool *done);
static CURLcode myssh_multi_statemach(struct Curl_easy *data,
bool *done);
static CURLcode myssh_do_it(struct Curl_easy *data, bool *done);
static CURLcode scp_done(struct Curl_easy *data,
CURLcode, 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 myssh_pollset(struct Curl_easy *data,
struct easy_pollset *ps);
static void myssh_block2waitfor(struct connectdata *conn,
struct ssh_conn *sshc,
bool block);
static CURLcode myssh_setup_connection(struct Curl_easy *data,
struct connectdata *conn);
static void sshc_cleanup(struct ssh_conn *sshc);
const struct Curl_handler Curl_handler_scp = {
"SCP",
myssh_setup_connection,
myssh_do_it,
scp_done,
ZERO_NULL,
myssh_connect,
myssh_multi_statemach,
scp_doing,
myssh_pollset,
myssh_pollset,
ZERO_NULL,
myssh_pollset,
scp_disconnect,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
PORT_SSH,
CURLPROTO_SCP,
CURLPROTO_SCP,
PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION | PROTOPT_NOURLQUERY
};
const struct Curl_handler Curl_handler_sftp = {
"SFTP",
myssh_setup_connection,
myssh_do_it,
sftp_done,
ZERO_NULL,
myssh_connect,
myssh_multi_statemach,
sftp_doing,
myssh_pollset,
myssh_pollset,
ZERO_NULL,
myssh_pollset,
sftp_disconnect,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
PORT_SSH,
CURLPROTO_SFTP,
CURLPROTO_SFTP,
PROTOPT_DIRLOCK | PROTOPT_CLOSEACTION
| PROTOPT_NOURLQUERY
};
static CURLcode sftp_error_to_CURLE(int err)
{
switch(err) {
case SSH_FX_OK:
return CURLE_OK;
case SSH_FX_NO_SUCH_FILE:
case SSH_FX_NO_SUCH_PATH:
return CURLE_REMOTE_FILE_NOT_FOUND;
case SSH_FX_PERMISSION_DENIED:
case SSH_FX_WRITE_PROTECT:
return CURLE_REMOTE_ACCESS_DENIED;
case SSH_FX_FILE_ALREADY_EXISTS:
return CURLE_REMOTE_FILE_EXISTS;
default:
break;
}
return CURLE_SSH;
}
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
#define myssh_to(x,y,z) myssh_set_state(x,y,z, __LINE__)
#else
#define myssh_to(x,y,z) myssh_set_state(x,y,z)
#endif
static void myssh_set_state(struct Curl_easy *data,
struct ssh_conn *sshc,
sshstate nowstate
#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
, int lineno
#endif
)
{
#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"
};
if(sshc->state != nowstate) {
infof(data, "SSH %p state change from %s to %s (line %d)",
(void *) sshc, names[sshc->state], names[nowstate],
lineno);
}
#endif
(void)data;
sshc->state = nowstate;
}
static int myssh_is_known(struct Curl_easy *data, struct ssh_conn *sshc)
{
int rc;
ssh_key pubkey;
size_t hlen;
unsigned char *hash = NULL;
char *found_base64 = NULL;
char *known_base64 = NULL;
int vstate;
enum curl_khmatch keymatch;
struct curl_khkey foundkey;
struct curl_khkey *knownkeyp = NULL;
curl_sshkeycallback func =
data->set.ssh_keyfunc;
struct ssh_knownhosts_entry *knownhostsentry = NULL;
struct curl_khkey knownkey;
rc = ssh_get_server_publickey(sshc->ssh_session, &pubkey);
if(rc != SSH_OK)
return rc;
if(data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5]) {
int i;
char md5buffer[33];
const char *pubkey_md5 = data->set.str[STRING_SSH_HOST_PUBLIC_KEY_MD5];
rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5,
&hash, &hlen);
if(rc != SSH_OK || hlen != 16) {
failf(data,
"Denied establishing ssh session: md5 fingerprint not available");
goto cleanup;
}
for(i = 0; i < 16; i++)
curl_msnprintf(&md5buffer[i*2], 3, "%02x", (unsigned char)hash[i]);
infof(data, "SSH MD5 fingerprint: %s", md5buffer);
if(!curl_strequal(md5buffer, pubkey_md5)) {
failf(data,
"Denied establishing ssh session: mismatch md5 fingerprint. "
"Remote %s is not equal to %s", md5buffer, pubkey_md5);
rc = SSH_ERROR;
goto cleanup;
}
rc = SSH_OK;
goto cleanup;
}
if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
vstate = ssh_session_get_known_hosts_entry(sshc->ssh_session,
&knownhostsentry);
if(knownhostsentry) {
if(knownhostsentry->publickey) {
rc = ssh_pki_export_pubkey_base64(knownhostsentry->publickey,
&known_base64);
if(rc != SSH_OK) {
goto cleanup;
}
knownkey.key = known_base64;
knownkey.len = strlen(known_base64);
switch(ssh_key_type(knownhostsentry->publickey)) {
case SSH_KEYTYPE_RSA:
knownkey.keytype = CURLKHTYPE_RSA;
break;
case SSH_KEYTYPE_RSA1:
knownkey.keytype = CURLKHTYPE_RSA1;
break;
case SSH_KEYTYPE_ECDSA:
case SSH_KEYTYPE_ECDSA_P256:
case SSH_KEYTYPE_ECDSA_P384:
case SSH_KEYTYPE_ECDSA_P521:
knownkey.keytype = CURLKHTYPE_ECDSA;
break;
case SSH_KEYTYPE_ED25519:
knownkey.keytype = CURLKHTYPE_ED25519;
break;
case SSH_KEYTYPE_DSS:
knownkey.keytype = CURLKHTYPE_DSS;
break;
default:
rc = SSH_ERROR;
goto cleanup;
}
knownkeyp = &knownkey;
}
}
switch(vstate) {
case SSH_KNOWN_HOSTS_OK:
keymatch = CURLKHMATCH_OK;
break;
case SSH_KNOWN_HOSTS_OTHER:
case SSH_KNOWN_HOSTS_NOT_FOUND:
case SSH_KNOWN_HOSTS_UNKNOWN:
case SSH_KNOWN_HOSTS_ERROR:
keymatch = CURLKHMATCH_MISSING;
break;
default:
keymatch = CURLKHMATCH_MISMATCH;
break;
}
if(func) {
rc = ssh_pki_export_pubkey_base64(pubkey, &found_base64);
if(rc != SSH_OK)
goto cleanup;
foundkey.key = found_base64;
foundkey.len = strlen(found_base64);
switch(ssh_key_type(pubkey)) {
case SSH_KEYTYPE_RSA:
foundkey.keytype = CURLKHTYPE_RSA;
break;
case SSH_KEYTYPE_RSA1:
foundkey.keytype = CURLKHTYPE_RSA1;
break;
case SSH_KEYTYPE_ECDSA:
case SSH_KEYTYPE_ECDSA_P256:
case SSH_KEYTYPE_ECDSA_P384:
case SSH_KEYTYPE_ECDSA_P521:
foundkey.keytype = CURLKHTYPE_ECDSA;
break;
case SSH_KEYTYPE_ED25519:
foundkey.keytype = CURLKHTYPE_ED25519;
break;
case SSH_KEYTYPE_DSS:
foundkey.keytype = CURLKHTYPE_DSS;
break;
default:
rc = SSH_ERROR;
goto cleanup;
}
Curl_set_in_callback(data, TRUE);
rc = func(data, knownkeyp,
&foundkey,
keymatch, data->set.ssh_keyfunc_userp);
Curl_set_in_callback(data, FALSE);
switch(rc) {
case CURLKHSTAT_FINE_ADD_TO_FILE:
rc = ssh_session_update_known_hosts(sshc->ssh_session);
if(rc != SSH_OK) {
goto cleanup;
}
break;
case CURLKHSTAT_FINE:
break;
default:
rc = SSH_ERROR;
goto cleanup;
}
}
else {
if(keymatch != CURLKHMATCH_OK) {
rc = SSH_ERROR;
goto cleanup;
}
}
}
rc = SSH_OK;
cleanup:
if(found_base64) {
(free)(found_base64);
}
if(known_base64) {
(free)(known_base64);
}
if(hash)
ssh_clean_pubkey_hash(&hash);
ssh_key_free(pubkey);
if(knownhostsentry) {
ssh_knownhosts_entry_free(knownhostsentry);
}
return rc;
}
static int myssh_to_ERROR(struct Curl_easy *data,
struct ssh_conn *sshc,
CURLcode result)
{
myssh_to(data, sshc, SSH_SESSION_DISCONNECT);
sshc->actualcode = result;
return SSH_ERROR;
}
static int myssh_to_SFTP_CLOSE(struct Curl_easy *data,
struct ssh_conn *sshc)
{
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->actualcode =
sftp_error_to_CURLE(sftp_get_error(sshc->sftp_session));
return SSH_ERROR;
}
static int myssh_to_PASSWD_AUTH(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) {
myssh_to(data, sshc, SSH_AUTH_PASS_INIT);
return SSH_OK;
}
return myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED);
}
static int myssh_to_KEY_AUTH(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) {
myssh_to(data, sshc, SSH_AUTH_KEY_INIT);
return SSH_OK;
}
return myssh_to_PASSWD_AUTH(data, sshc);
}
static int myssh_to_GSSAPI_AUTH(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) {
myssh_to(data, sshc, SSH_AUTH_GSSAPI);
return SSH_OK;
}
return myssh_to_KEY_AUTH(data, sshc);
}
static int myssh_in_SFTP_READDIR_INIT(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
Curl_pgrsSetDownloadSize(data, -1);
if(data->req.no_body) {
myssh_to(data, sshc, SSH_STOP);
return SSH_NO_ERROR;
}
sshc->sftp_dir = sftp_opendir(sshc->sftp_session,
sshp->path);
if(!sshc->sftp_dir) {
failf(data, "Could not open directory for reading: %s",
ssh_get_error(sshc->ssh_session));
return myssh_to_SFTP_CLOSE(data, sshc);
}
myssh_to(data, sshc, SSH_SFTP_READDIR);
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_READDIR(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
CURLcode result = CURLE_OK;
curlx_dyn_reset(&sshc->readdir_buf);
if(sshc->readdir_attrs)
sftp_attributes_free(sshc->readdir_attrs);
sshc->readdir_attrs = sftp_readdir(sshc->sftp_session, sshc->sftp_dir);
if(sshc->readdir_attrs) {
sshc->readdir_filename = sshc->readdir_attrs->name;
sshc->readdir_longentry = sshc->readdir_attrs->longname;
sshc->readdir_len = strlen(sshc->readdir_filename);
if(data->set.list_only) {
char *tmpLine;
tmpLine = curl_maprintf("%s\n", sshc->readdir_filename);
if(!tmpLine) {
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->actualcode = CURLE_OUT_OF_MEMORY;
return SSH_ERROR;
}
result = Curl_client_write(data, CLIENTWRITE_BODY,
tmpLine, sshc->readdir_len + 1);
free(tmpLine);
if(result) {
myssh_to(data, sshc, SSH_STOP);
sshc->actualcode = result;
return SSH_NO_ERROR;
}
}
else {
if(curlx_dyn_add(&sshc->readdir_buf, sshc->readdir_longentry)) {
sshc->actualcode = CURLE_OUT_OF_MEMORY;
myssh_to(data, sshc, SSH_STOP);
return SSH_ERROR;
}
if((sshc->readdir_attrs->flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
((sshc->readdir_attrs->permissions & SSH_S_IFMT) ==
SSH_S_IFLNK)) {
sshc->readdir_linkPath = curl_maprintf("%s%s", sshp->path,
sshc->readdir_filename);
if(!sshc->readdir_linkPath) {
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->actualcode = CURLE_OUT_OF_MEMORY;
return SSH_ERROR;
}
myssh_to(data, sshc, SSH_SFTP_READDIR_LINK);
return SSH_NO_ERROR;
}
myssh_to(data, sshc, SSH_SFTP_READDIR_BOTTOM);
return SSH_NO_ERROR;
}
}
else if(sftp_dir_eof(sshc->sftp_dir)) {
myssh_to(data, sshc, SSH_SFTP_READDIR_DONE);
}
else {
failf(data, "Could not open remote directory for reading: %s",
ssh_get_error(sshc->ssh_session));
return myssh_to_SFTP_CLOSE(data, sshc);
}
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_READDIR_LINK(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if(sshc->readdir_link_attrs)
sftp_attributes_free(sshc->readdir_link_attrs);
sshc->readdir_link_attrs = sftp_lstat(sshc->sftp_session,
sshc->readdir_linkPath);
if(!sshc->readdir_link_attrs) {
failf(data, "Could not read symlink for reading: %s",
ssh_get_error(sshc->ssh_session));
return myssh_to_SFTP_CLOSE(data, sshc);
}
if(!sshc->readdir_link_attrs->name) {
sshc->readdir_tmp = sftp_readlink(sshc->sftp_session,
sshc->readdir_linkPath);
if(!sshc->readdir_tmp)
sshc->readdir_len = 0;
else
sshc->readdir_len = strlen(sshc->readdir_tmp);
sshc->readdir_longentry = NULL;
sshc->readdir_filename = sshc->readdir_tmp;
}
else {
sshc->readdir_len = strlen(sshc->readdir_link_attrs->name);
sshc->readdir_filename = sshc->readdir_link_attrs->name;
sshc->readdir_longentry = sshc->readdir_link_attrs->longname;
}
Curl_safefree(sshc->readdir_linkPath);
if(curlx_dyn_addf(&sshc->readdir_buf, " -> %s",
sshc->readdir_filename)) {
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->actualcode = CURLE_OUT_OF_MEMORY;
return SSH_ERROR;
}
sftp_attributes_free(sshc->readdir_link_attrs);
sshc->readdir_link_attrs = NULL;
sshc->readdir_filename = NULL;
sshc->readdir_longentry = NULL;
myssh_to(data, sshc, SSH_SFTP_READDIR_BOTTOM);
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_READDIR_BOTTOM(struct Curl_easy *data,
struct ssh_conn *sshc)
{
CURLcode result;
if(curlx_dyn_addn(&sshc->readdir_buf, "\n", 1))
result = CURLE_OUT_OF_MEMORY;
else
result = Curl_client_write(data, CLIENTWRITE_BODY,
curlx_dyn_ptr(&sshc->readdir_buf),
curlx_dyn_len(&sshc->readdir_buf));
ssh_string_free_char(sshc->readdir_tmp);
sshc->readdir_tmp = NULL;
if(result)
myssh_to(data, sshc, SSH_STOP);
else
myssh_to(data, sshc, SSH_SFTP_READDIR);
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_READDIR_DONE(struct Curl_easy *data,
struct ssh_conn *sshc)
{
sftp_closedir(sshc->sftp_dir);
sshc->sftp_dir = NULL;
Curl_xfer_setup_nop(data);
myssh_to(data, sshc, SSH_STOP);
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_QUOTE_STATVFS(struct Curl_easy *data,
struct ssh_conn *sshc)
{
sftp_statvfs_t statvfs;
statvfs = sftp_statvfs(sshc->sftp_session, sshc->quote_path1);
if(!statvfs && !sshc->acceptfail) {
Curl_safefree(sshc->quote_path1);
failf(data, "statvfs command failed: %s",
ssh_get_error(sshc->ssh_session));
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
return SSH_OK;
}
else if(statvfs) {
#ifdef _MSC_VER
#define CURL_LIBSSH_VFS_SIZE_MASK "I64u"
#else
#define CURL_LIBSSH_VFS_SIZE_MASK PRIu64
#endif
CURLcode result = CURLE_OK;
char *tmp = curl_maprintf("statvfs:\n"
"f_bsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_frsize: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_blocks: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_bfree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_bavail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_files: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_ffree: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_favail: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_fsid: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_flag: %" CURL_LIBSSH_VFS_SIZE_MASK "\n"
"f_namemax: %" CURL_LIBSSH_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);
sftp_statvfs_free(statvfs);
if(!tmp)
result = CURLE_OUT_OF_MEMORY;
if(!result) {
result = Curl_client_write(data, CLIENTWRITE_HEADER, tmp, strlen(tmp));
free(tmp);
}
if(result) {
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
}
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
return SSH_OK;
}
static int myssh_auth_interactive(struct connectdata *conn,
struct ssh_conn *sshc)
{
int rc;
int nprompts;
restart:
switch(sshc->kbd_state) {
case 0:
rc = ssh_userauth_kbdint(sshc->ssh_session, NULL, NULL);
if(rc == SSH_AUTH_AGAIN)
return SSH_AGAIN;
if(rc != SSH_AUTH_INFO)
return SSH_ERROR;
nprompts = ssh_userauth_kbdint_getnprompts(sshc->ssh_session);
if(nprompts != 1)
return SSH_ERROR;
rc = ssh_userauth_kbdint_setanswer(sshc->ssh_session, 0, conn->passwd);
if(rc < 0)
return SSH_ERROR;
FALLTHROUGH();
case 1:
sshc->kbd_state = 1;
rc = ssh_userauth_kbdint(sshc->ssh_session, NULL, NULL);
if(rc == SSH_AUTH_AGAIN)
return SSH_AGAIN;
else if(rc == SSH_AUTH_SUCCESS)
rc = SSH_OK;
else if(rc == SSH_AUTH_INFO) {
nprompts = ssh_userauth_kbdint_getnprompts(sshc->ssh_session);
if(nprompts)
return SSH_ERROR;
sshc->kbd_state = 2;
goto restart;
}
else
rc = SSH_ERROR;
break;
case 2:
sshc->kbd_state = 2;
rc = ssh_userauth_kbdint(sshc->ssh_session, NULL, NULL);
if(rc == SSH_AUTH_AGAIN)
return SSH_AGAIN;
else if(rc == SSH_AUTH_SUCCESS)
rc = SSH_OK;
else
rc = SSH_ERROR;
break;
default:
return SSH_ERROR;
}
sshc->kbd_state = 0;
return rc;
}
static void myssh_state_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
sshc->secondCreateDirs = 0;
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_OK;
#if 0#endif
ssh_set_blocking(sshc->ssh_session, 0);
myssh_to(data, sshc, SSH_S_STARTUP);
}
static int myssh_in_S_STARTUP(struct Curl_easy *data,
struct ssh_conn *sshc)
{
struct connectdata *conn = data->conn;
int rc = ssh_connect(sshc->ssh_session);
myssh_block2waitfor(conn, sshc, (rc == SSH_AGAIN));
if(rc == SSH_AGAIN) {
DEBUGF(infof(data, "ssh_connect -> EAGAIN"));
}
else if(rc != SSH_OK) {
failf(data, "Failure establishing ssh session");
rc = myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
}
else
myssh_to(data, sshc, SSH_HOSTKEY);
return rc;
}
static int myssh_in_AUTHLIST(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc;
sshc->authed = FALSE;
rc = ssh_userauth_none(sshc->ssh_session, NULL);
if(rc == SSH_AUTH_AGAIN)
return SSH_AGAIN;
if(rc == SSH_AUTH_SUCCESS) {
sshc->authed = TRUE;
infof(data, "Authenticated with none");
myssh_to(data, sshc, SSH_AUTH_DONE);
return rc;
}
else if(rc == SSH_AUTH_ERROR) {
rc = myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED);
return rc;
}
sshc->auth_methods =
(unsigned int)ssh_userauth_list(sshc->ssh_session, NULL);
if(sshc->auth_methods)
infof(data, "SSH authentication methods available: %s%s%s%s",
sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY ?
"public key, ": "",
sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC ?
"GSSAPI, " : "",
sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE ?
"keyboard-interactive, " : "",
sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD ?
"password": "");
if(sshc->auth_methods & SSH_AUTH_METHOD_PUBLICKEY) {
myssh_to(data, sshc, SSH_AUTH_PKEY_INIT);
infof(data, "Authentication using SSH public key file");
}
else if(sshc->auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) {
myssh_to(data, sshc, SSH_AUTH_GSSAPI);
}
else if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) {
myssh_to(data, sshc, SSH_AUTH_KEY_INIT);
}
else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) {
myssh_to(data, sshc, SSH_AUTH_PASS_INIT);
}
else {
rc = myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED);
}
return rc;
}
static int myssh_in_AUTH_PKEY_INIT(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc;
if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) {
rc = myssh_to_GSSAPI_AUTH(data, sshc);
return rc;
}
if(data->set.str[STRING_SSH_PRIVATE_KEY]) {
if(sshc->pubkey && !data->set.ssl.key_passwd) {
rc = ssh_userauth_try_publickey(sshc->ssh_session, NULL,
sshc->pubkey);
if(rc == SSH_AUTH_AGAIN)
return SSH_AGAIN;
if(rc != SSH_OK) {
rc = myssh_to_GSSAPI_AUTH(data, sshc);
return rc;
}
}
rc = ssh_pki_import_privkey_file(data->
set.str[STRING_SSH_PRIVATE_KEY],
data->set.ssl.key_passwd, NULL,
NULL, &sshc->privkey);
if(rc != SSH_OK) {
failf(data, "Could not load private key file %s",
data->set.str[STRING_SSH_PRIVATE_KEY]);
rc = myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED);
return rc;
}
myssh_to(data, sshc, SSH_AUTH_PKEY);
}
else {
rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL,
data->set.ssl.key_passwd);
if(rc == SSH_AUTH_AGAIN)
return SSH_AGAIN;
if(rc == SSH_AUTH_SUCCESS) {
rc = SSH_OK;
sshc->authed = TRUE;
infof(data, "Completed public key authentication");
myssh_to(data, sshc, SSH_AUTH_DONE);
return rc;
}
rc = myssh_to_GSSAPI_AUTH(data, sshc);
}
return rc;
}
static int myssh_in_AUTH_PKEY(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = ssh_userauth_publickey(sshc->ssh_session, NULL, sshc->privkey);
if(rc == SSH_AUTH_AGAIN)
return SSH_AGAIN;
else if(rc == SSH_AUTH_SUCCESS) {
sshc->authed = TRUE;
infof(data, "Completed public key authentication");
myssh_to(data, sshc, SSH_AUTH_DONE);
return SSH_OK;
}
else {
infof(data, "Failed public key authentication (rc: %d)", rc);
return myssh_to_GSSAPI_AUTH(data, sshc);
}
}
static int myssh_in_AUTH_GSSAPI(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc;
if(!(data->set.ssh_auth_types & CURLSSH_AUTH_GSSAPI))
return myssh_to_KEY_AUTH(data, sshc);
rc = ssh_userauth_gssapi(sshc->ssh_session);
if(rc == SSH_AUTH_AGAIN)
return SSH_AGAIN;
if(rc == SSH_AUTH_SUCCESS) {
sshc->authed = TRUE;
infof(data, "Completed gssapi authentication");
myssh_to(data, sshc, SSH_AUTH_DONE);
return SSH_OK;
}
return myssh_to_KEY_AUTH(data, sshc);
}
static int myssh_in_AUTH_KEY_INIT(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if(data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD) {
myssh_to(data, sshc, SSH_AUTH_KEY);
return SSH_NO_ERROR;
}
return myssh_to_PASSWD_AUTH(data, sshc);
}
static int myssh_in_AUTH_KEY(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = myssh_auth_interactive(data->conn, sshc);
if(rc == SSH_AGAIN)
return rc;
else if(rc == SSH_OK) {
sshc->authed = TRUE;
infof(data, "completed keyboard interactive authentication");
myssh_to(data, sshc, SSH_AUTH_DONE);
return SSH_NO_ERROR;
}
else
return myssh_to_PASSWD_AUTH(data, sshc);
}
static int myssh_in_AUTH_PASS_INIT(struct Curl_easy *data,
struct ssh_conn *sshc)
{
if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD))
return myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED);
myssh_to(data, sshc, SSH_AUTH_PASS);
return SSH_NO_ERROR;
}
static int myssh_in_AUTH_PASS(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc = ssh_userauth_password(sshc->ssh_session, NULL, data->conn->passwd);
if(rc == SSH_AUTH_AGAIN)
return SSH_AGAIN;
else if(rc == SSH_AUTH_SUCCESS) {
sshc->authed = TRUE;
infof(data, "Completed password authentication");
myssh_to(data, sshc, SSH_AUTH_DONE);
return SSH_NO_ERROR;
}
return myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED);
}
static int myssh_in_AUTH_DONE(struct Curl_easy *data,
struct ssh_conn *sshc)
{
struct connectdata *conn = data->conn;
if(!sshc->authed) {
failf(data, "Authentication failure");
return myssh_to_ERROR(data, sshc, CURLE_LOGIN_DENIED);
}
infof(data, "Authentication complete");
Curl_pgrsTime(data, TIMER_APPCONNECT);
conn->recv_idx = FIRSTSOCKET;
conn->send_idx = -1;
if(conn->handler->protocol == CURLPROTO_SFTP) {
myssh_to(data, sshc, SSH_SFTP_INIT);
return SSH_NO_ERROR;
}
infof(data, "SSH CONNECT phase done");
myssh_to(data, sshc, SSH_STOP);
return SSH_NO_ERROR;
}
static int myssh_in_UPLOAD_INIT(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
int flags;
int rc = 0;
if(data->state.resume_from) {
sftp_attributes attrs;
if(data->state.resume_from < 0) {
attrs = sftp_stat(sshc->sftp_session, sshp->path);
if(attrs) {
curl_off_t size = attrs->size;
if(size < 0) {
failf(data, "Bad file size (%" FMT_OFF_T ")", size);
rc = myssh_to_ERROR(data, sshc, CURLE_BAD_DOWNLOAD_RESUME);
return rc;
}
data->state.resume_from = attrs->size;
sftp_attributes_free(attrs);
}
else {
data->state.resume_from = 0;
}
}
}
if(data->set.remote_append) {
flags = O_WRONLY | O_CREAT | O_APPEND;
}
else if(data->state.resume_from > 0) {
flags = O_WRONLY;
}
else
flags = O_WRONLY|O_CREAT|O_TRUNC;
if(sshc->sftp_file)
sftp_close(sshc->sftp_file);
sshc->sftp_file =
sftp_open(sshc->sftp_session, sshp->path,
flags, (mode_t)data->set.new_file_perms);
if(!sshc->sftp_file) {
int err = sftp_get_error(sshc->sftp_session);
if(((err == SSH_FX_NO_SUCH_FILE || err == SSH_FX_FAILURE ||
err == SSH_FX_NO_SUCH_PATH)) &&
(data->set.ftp_create_missing_dirs &&
(strlen(sshp->path) > 1))) {
rc = 0;
sshc->secondCreateDirs = 1;
myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS_INIT);
return rc;
}
else {
rc = myssh_to_SFTP_CLOSE(data, sshc);
return rc;
}
}
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");
rc = myssh_to_ERROR(data, sshc, CURLE_FTP_COULDNT_USE_REST);
return rc;
}
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 =
data->state.fread_func(scratch, 1,
readthisamountnow, data->state.in);
passed += actuallyread;
if((actuallyread == 0) || (actuallyread > readthisamountnow)) {
failf(data, "Failed to read data");
rc = myssh_to_ERROR(data, sshc, CURLE_FTP_COULDNT_USE_REST);
return rc;
}
} while(passed < data->state.resume_from);
}
if(data->state.infilesize > 0) {
if(data->state.resume_from > data->state.infilesize) {
failf(data, "Resume point beyond size");
return myssh_to_ERROR(data, sshc, CURLE_BAD_FUNCTION_ARGUMENT);
}
data->state.infilesize -= data->state.resume_from;
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->state.infilesize);
}
rc = sftp_seek64(sshc->sftp_file, data->state.resume_from);
if(rc) {
rc = myssh_to_SFTP_CLOSE(data, sshc);
return rc;
}
}
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);
#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0)
sshc->sftp_send_state = 0;
#endif
myssh_to(data, sshc, SSH_STOP);
return rc;
}
static int myssh_in_SFTP_DOWNLOAD_INIT(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
if(sshc->sftp_file)
sftp_close(sshc->sftp_file);
sshc->sftp_file = sftp_open(sshc->sftp_session, sshp->path,
O_RDONLY, (mode_t)data->set.new_file_perms);
if(!sshc->sftp_file) {
failf(data, "Could not open remote file for reading: %s",
ssh_get_error(sshc->ssh_session));
return myssh_to_SFTP_CLOSE(data, sshc);
}
sftp_file_set_nonblocking(sshc->sftp_file);
myssh_to(data, sshc, SSH_SFTP_DOWNLOAD_STAT);
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_DOWNLOAD_STAT(struct Curl_easy *data,
struct ssh_conn *sshc)
{
curl_off_t size;
int rc = 0;
sftp_attributes attrs = sftp_fstat(sshc->sftp_file);
if(!attrs ||
!(attrs->flags & SSH_FILEXFER_ATTR_SIZE) ||
(attrs->size == 0)) {
data->req.size = -1;
data->req.maxdownload = -1;
Curl_pgrsSetDownloadSize(data, -1);
size = 0;
if(attrs)
sftp_attributes_free(attrs);
}
else {
size = attrs->size;
sftp_attributes_free(attrs);
if(size < 0) {
failf(data, "Bad file size (%" FMT_OFF_T ")", size);
return myssh_to_ERROR(data, sshc, CURLE_BAD_DOWNLOAD_RESUME);
}
if(data->state.use_range) {
curl_off_t from, to;
const char *p = data->state.range;
int from_t, to_t;
from_t = curlx_str_number(&p, &from, CURL_OFF_T_MAX);
if(from_t == STRE_OVERFLOW)
return myssh_to_ERROR(data, sshc, 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 myssh_to_ERROR(data, sshc, CURLE_RANGE_ERROR);
if((to_t == STRE_NO_NUM) || (to >= size)) {
to = size - 1;
}
if(from_t == STRE_NO_NUM) {
from = size - to;
to = size - 1;
}
if(from > size) {
failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%"
FMT_OFF_T ")", from, size);
return myssh_to_ERROR(data, sshc, CURLE_BAD_DOWNLOAD_RESUME);
}
if(from > to) {
from = to;
size = 0;
}
else {
if((to - from) == CURL_OFF_T_MAX)
return myssh_to_ERROR(data, sshc, CURLE_RANGE_ERROR);
size = to - from + 1;
}
rc = sftp_seek64(sshc->sftp_file, from);
if(rc)
return myssh_to_SFTP_CLOSE(data, sshc);
}
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)size < -data->state.resume_from) {
failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%"
FMT_OFF_T ")", data->state.resume_from, size);
return myssh_to_ERROR(data, sshc, CURLE_BAD_DOWNLOAD_RESUME);
}
data->state.resume_from += size;
}
else {
if((curl_off_t)size < data->state.resume_from) {
failf(data, "Offset (%" FMT_OFF_T
") was beyond file size (%" FMT_OFF_T ")",
data->state.resume_from, size);
return myssh_to_ERROR(data, sshc, CURLE_BAD_DOWNLOAD_RESUME);
}
}
data->req.size = size - data->state.resume_from;
data->req.maxdownload = size - data->state.resume_from;
Curl_pgrsSetDownloadSize(data,
size - data->state.resume_from);
rc = sftp_seek64(sshc->sftp_file, data->state.resume_from);
if(rc)
return myssh_to_SFTP_CLOSE(data, sshc);
}
if(data->req.size == 0) {
Curl_xfer_setup_nop(data);
infof(data, "File already completely downloaded");
myssh_to(data, sshc, SSH_STOP);
return rc;
}
Curl_xfer_setup_recv(data, FIRSTSOCKET, data->req.size);
data->conn->send_idx = 0;
sshc->sftp_recv_state = 0;
myssh_to(data, sshc, SSH_STOP);
return rc;
}
static int myssh_in_SFTP_CLOSE(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
if(sshc->sftp_file) {
sftp_close(sshc->sftp_file);
sshc->sftp_file = NULL;
}
Curl_safefree(sshp->path);
DEBUGF(infof(data, "SFTP DONE done"));
if(sshc->nextstate != SSH_NO_STATE &&
sshc->nextstate != SSH_SFTP_CLOSE) {
myssh_to(data, sshc, sshc->nextstate);
sshc->nextstate = SSH_SFTP_CLOSE;
}
else {
myssh_to(data, sshc, SSH_STOP);
}
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_SHUTDOWN(struct Curl_easy *data,
struct ssh_conn *sshc)
{
ssh_set_blocking(sshc->ssh_session, 0);
#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0)
SFTP_AIO_FREE(sshc->sftp_send_aio);
SFTP_AIO_FREE(sshc->sftp_recv_aio);
#endif
if(sshc->sftp_file) {
sftp_close(sshc->sftp_file);
sshc->sftp_file = NULL;
}
if(sshc->sftp_session) {
sftp_free(sshc->sftp_session);
sshc->sftp_session = NULL;
}
SSH_STRING_FREE_CHAR(sshc->homedir);
myssh_to(data, sshc, SSH_SESSION_DISCONNECT);
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_INIT(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc;
ssh_set_blocking(sshc->ssh_session, 1);
sshc->sftp_session = sftp_new(sshc->ssh_session);
if(!sshc->sftp_session) {
failf(data, "Failure initializing sftp session: %s",
ssh_get_error(sshc->ssh_session));
return myssh_to_ERROR(data, sshc, CURLE_COULDNT_CONNECT);
}
rc = sftp_init(sshc->sftp_session);
if(rc != SSH_OK) {
failf(data, "Failure initializing sftp session: %s",
ssh_get_error(sshc->ssh_session));
return myssh_to_ERROR(data, sshc, sftp_error_to_CURLE(SSH_FX_FAILURE));
}
myssh_to(data, sshc, SSH_SFTP_REALPATH);
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_REALPATH(struct Curl_easy *data,
struct ssh_conn *sshc)
{
sshc->homedir = sftp_canonicalize_path(sshc->sftp_session, ".");
if(!sshc->homedir)
return myssh_to_ERROR(data, sshc, CURLE_COULDNT_CONNECT);
free(data->state.most_recent_ftp_entrypath);
data->state.most_recent_ftp_entrypath = strdup(sshc->homedir);
if(!data->state.most_recent_ftp_entrypath)
return myssh_to_ERROR(data, sshc, CURLE_OUT_OF_MEMORY);
DEBUGF(infof(data, "SSH CONNECT phase done"));
myssh_to(data, sshc, SSH_STOP);
return SSH_NO_ERROR;
}
static int myssh_in_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) {
sshc->actualcode = result;
myssh_to(data, sshc, SSH_STOP);
}
else if(data->set.quote) {
infof(data, "Sending quote commands");
sshc->quote_item = data->set.quote;
myssh_to(data, sshc, SSH_SFTP_QUOTE);
}
else
myssh_to(data, sshc, SSH_SFTP_GETINFO);
return SSH_NO_ERROR;
}
static int myssh_in_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_to(data, sshc, SSH_SFTP_QUOTE);
}
else {
myssh_to(data, sshc, SSH_STOP);
}
return SSH_NO_ERROR;
}
static int 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);
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_QUOTE(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
const char *cp;
CURLcode result;
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) {
sshc->actualcode = CURLE_OUT_OF_MEMORY;
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
return SSH_NO_ERROR;
}
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_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
}
else
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
return SSH_NO_ERROR;
}
cp = strchr(cmd, ' ');
if(!cp) {
failf(data, "Syntax error in SFTP command. Supply parameter(s)");
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
return SSH_NO_ERROR;
}
result = Curl_get_pathname(&cp, &sshc->quote_path1, sshc->homedir);
if(result) {
if(result == CURLE_OUT_OF_MEMORY)
failf(data, "Out of memory");
else
failf(data, "Syntax error: Bad first parameter");
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
return SSH_NO_ERROR;
}
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, "Out of memory");
else
failf(data, "Syntax error in chgrp/chmod/chown/atime/mtime: "
"Bad second parameter");
Curl_safefree(sshc->quote_path1);
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
return SSH_NO_ERROR;
}
if(*cp)
return return_quote_error(data, sshc);
sshc->quote_attrs = NULL;
myssh_to(data, sshc, SSH_SFTP_QUOTE_STAT);
return SSH_NO_ERROR;
}
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, "Out of memory");
else
failf(data, "Syntax error in ln/symlink: Bad second parameter");
Curl_safefree(sshc->quote_path1);
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
return SSH_NO_ERROR;
}
if(*cp)
return return_quote_error(data, sshc);
myssh_to(data, sshc, SSH_SFTP_QUOTE_SYMLINK);
return SSH_NO_ERROR;
}
else if(!strncmp(cmd, "mkdir ", 6)) {
if(*cp)
return return_quote_error(data, sshc);
myssh_to(data, sshc, SSH_SFTP_QUOTE_MKDIR);
return SSH_NO_ERROR;
}
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, "Out of memory");
else
failf(data, "Syntax error in rename: Bad second parameter");
Curl_safefree(sshc->quote_path1);
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = result;
return SSH_NO_ERROR;
}
if(*cp)
return return_quote_error(data, sshc);
myssh_to(data, sshc, SSH_SFTP_QUOTE_RENAME);
return SSH_NO_ERROR;
}
else if(!strncmp(cmd, "rmdir ", 6)) {
if(*cp)
return return_quote_error(data, sshc);
myssh_to(data, sshc, SSH_SFTP_QUOTE_RMDIR);
return SSH_NO_ERROR;
}
else if(!strncmp(cmd, "rm ", 3)) {
if(*cp)
return return_quote_error(data, sshc);
myssh_to(data, sshc, SSH_SFTP_QUOTE_UNLINK);
return SSH_NO_ERROR;
}
#ifdef HAS_STATVFS_SUPPORT
else if(!strncmp(cmd, "statvfs ", 8)) {
if(*cp)
return return_quote_error(data, sshc);
myssh_to(data, sshc, SSH_SFTP_QUOTE_STATVFS);
return SSH_NO_ERROR;
}
#endif
failf(data, "Unknown SFTP command");
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
return SSH_NO_ERROR;
}
static int myssh_in_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_to(data, sshc, SSH_SFTP_QUOTE);
}
else {
if(sshc->nextstate != SSH_NO_STATE) {
myssh_to(data, sshc, sshc->nextstate);
sshc->nextstate = SSH_NO_STATE;
}
else {
myssh_to(data, sshc, SSH_SFTP_GETINFO);
}
}
return SSH_NO_ERROR;
}
static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data,
struct ssh_conn *sshc)
{
char *cmd = sshc->quote_item->data;
sshc->acceptfail = FALSE;
if(cmd[0] == '*') {
cmd++;
sshc->acceptfail = TRUE;
}
if(sshc->quote_attrs)
sftp_attributes_free(sshc->quote_attrs);
sshc->quote_attrs = sftp_stat(sshc->sftp_session, sshc->quote_path2);
if(!sshc->quote_attrs) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Attempt to get SFTP stats failed: %d",
sftp_get_error(sshc->sftp_session));
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
return SSH_NO_ERROR;
}
if(!strncmp(cmd, "chgrp", 5)) {
const char *p = sshc->quote_path1;
curl_off_t gid;
if(curlx_str_number(&p, &gid, UINT_MAX)) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Syntax error: chgrp gid not a number");
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
return SSH_NO_ERROR;
}
sshc->quote_attrs->gid = (uint32_t)gid;
sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID;
}
else if(!strncmp(cmd, "chmod", 5)) {
curl_off_t perms;
const char *p = sshc->quote_path1;
if(curlx_str_octal(&p, &perms, 07777)) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Syntax error: chmod permissions not a number");
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
return SSH_NO_ERROR;
}
sshc->quote_attrs->permissions = (mode_t)perms;
sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_PERMISSIONS;
}
else if(!strncmp(cmd, "chown", 5)) {
const char *p = sshc->quote_path1;
curl_off_t uid;
if(curlx_str_number(&p, &uid, UINT_MAX)) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Syntax error: chown uid not a number");
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
return SSH_NO_ERROR;
}
sshc->quote_attrs->uid = (uint32_t)uid;
sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_UIDGID;
}
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 > 4
else if(date > 0xffffffff) {
failf(data, "date overflow");
fail = TRUE;
}
#endif
if(fail) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
return SSH_NO_ERROR;
}
if(date > UINT_MAX)
date = UINT_MAX;
if(!strncmp(cmd, "atime", 5))
sshc->quote_attrs->atime = (uint32_t)date;
else
sshc->quote_attrs->mtime = (uint32_t)date;
sshc->quote_attrs->flags |= SSH_FILEXFER_ATTR_ACMODTIME;
}
myssh_to(data, sshc, SSH_SFTP_QUOTE_SETSTAT);
return SSH_NO_ERROR;
}
static CURLcode myssh_statemach_act(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp,
bool *block)
{
CURLcode result = CURLE_OK;
struct connectdata *conn = data->conn;
int rc = SSH_NO_ERROR, err;
const char *err_msg;
*block = FALSE;
do {
switch(sshc->state) {
case SSH_INIT:
myssh_state_init(data, sshc);
FALLTHROUGH();
case SSH_S_STARTUP:
rc = myssh_in_S_STARTUP(data, sshc);
if(rc)
break;
FALLTHROUGH();
case SSH_HOSTKEY:
rc = myssh_is_known(data, sshc);
if(rc != SSH_OK) {
rc = myssh_to_ERROR(data, sshc, CURLE_PEER_FAILED_VERIFICATION);
break;
}
myssh_to(data, sshc, SSH_AUTHLIST);
FALLTHROUGH();
case SSH_AUTHLIST:
rc = myssh_in_AUTHLIST(data, sshc);
break;
case SSH_AUTH_PKEY_INIT:
rc = myssh_in_AUTH_PKEY_INIT(data, sshc);
break;
case SSH_AUTH_PKEY:
rc = myssh_in_AUTH_PKEY(data, sshc);
break;
case SSH_AUTH_GSSAPI:
rc = myssh_in_AUTH_GSSAPI(data, sshc);
break;
case SSH_AUTH_KEY_INIT:
rc = myssh_in_AUTH_KEY_INIT(data, sshc);
break;
case SSH_AUTH_KEY:
rc = myssh_in_AUTH_KEY(data, sshc);
break;
case SSH_AUTH_PASS_INIT:
rc = myssh_in_AUTH_PASS_INIT(data, sshc);
break;
case SSH_AUTH_PASS:
rc = myssh_in_AUTH_PASS(data, sshc);
break;
case SSH_AUTH_DONE:
rc = myssh_in_AUTH_DONE(data, sshc);
break;
case SSH_SFTP_INIT:
rc = myssh_in_SFTP_INIT(data, sshc);
break;
case SSH_SFTP_REALPATH:
rc = myssh_in_SFTP_REALPATH(data, sshc);
break;
case SSH_SFTP_QUOTE_INIT:
rc = sshp ? myssh_in_SFTP_QUOTE_INIT(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_POSTQUOTE_INIT:
rc = myssh_in_SFTP_POSTQUOTE_INIT(data, sshc);
break;
case SSH_SFTP_QUOTE:
rc = sshp ? myssh_in_SFTP_QUOTE(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_NEXT_QUOTE:
rc = myssh_in_SFTP_NEXT_QUOTE(data, sshc);
break;
case SSH_SFTP_QUOTE_STAT:
rc = myssh_in_SFTP_QUOTE_STAT(data, sshc);
break;
case SSH_SFTP_QUOTE_SETSTAT:
rc = sftp_setstat(sshc->sftp_session, sshc->quote_path2,
sshc->quote_attrs);
if(rc == SSH_AGAIN)
break;
if(rc && !sshc->acceptfail) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "Attempt to set SFTP stats failed: %s",
ssh_get_error(sshc->ssh_session));
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_SYMLINK:
rc = sftp_symlink(sshc->sftp_session, sshc->quote_path2,
sshc->quote_path1);
if(rc == SSH_AGAIN)
break;
if(rc && !sshc->acceptfail) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "symlink command failed: %s",
ssh_get_error(sshc->ssh_session));
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_MKDIR:
rc = sftp_mkdir(sshc->sftp_session, sshc->quote_path1,
(mode_t)data->set.new_directory_perms);
if(rc == SSH_AGAIN)
break;
if(rc && !sshc->acceptfail) {
Curl_safefree(sshc->quote_path1);
failf(data, "mkdir command failed: %s",
ssh_get_error(sshc->ssh_session));
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_RENAME:
rc = sftp_rename(sshc->sftp_session, sshc->quote_path1,
sshc->quote_path2);
if(rc == SSH_AGAIN)
break;
if(rc && !sshc->acceptfail) {
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
failf(data, "rename command failed: %s",
ssh_get_error(sshc->ssh_session));
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_RMDIR:
rc = sftp_rmdir(sshc->sftp_session, sshc->quote_path1);
if(rc == SSH_AGAIN)
break;
if(rc && !sshc->acceptfail) {
Curl_safefree(sshc->quote_path1);
failf(data, "rmdir command failed: %s",
ssh_get_error(sshc->ssh_session));
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_UNLINK:
rc = sftp_unlink(sshc->sftp_session, sshc->quote_path1);
if(rc == SSH_AGAIN)
break;
if(rc && !sshc->acceptfail) {
Curl_safefree(sshc->quote_path1);
failf(data, "rm command failed: %s",
ssh_get_error(sshc->ssh_session));
myssh_to(data, sshc, SSH_SFTP_CLOSE);
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_QUOTE_ERROR;
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
break;
case SSH_SFTP_QUOTE_STATVFS:
rc = myssh_in_SFTP_QUOTE_STATVFS(data, sshc);
break;
case SSH_SFTP_GETINFO:
if(data->set.get_filetime) {
myssh_to(data, sshc, SSH_SFTP_FILETIME);
}
else {
myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
}
break;
case SSH_SFTP_FILETIME: {
sftp_attributes attrs;
if(!sshp) {
result = CURLE_FAILED_INIT;
break;
}
attrs = sftp_stat(sshc->sftp_session, sshp->path);
if(attrs) {
data->info.filetime = attrs->mtime;
sftp_attributes_free(attrs);
}
myssh_to(data, sshc, SSH_SFTP_TRANS_INIT);
break;
}
case SSH_SFTP_TRANS_INIT:
if(data->state.upload)
myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
else if(sshp) {
if(sshp->path[strlen(sshp->path)-1] == '/')
myssh_to(data, sshc, SSH_SFTP_READDIR_INIT);
else
myssh_to(data, sshc, SSH_SFTP_DOWNLOAD_INIT);
}
else
result = CURLE_FAILED_INIT;
break;
case SSH_SFTP_UPLOAD_INIT:
rc = sshp ? myssh_in_UPLOAD_INIT(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_CREATE_DIRS_INIT:
if(!sshp) {
result = CURLE_FAILED_INIT;
break;
}
else if(strlen(sshp->path) > 1) {
sshc->slash_pos = sshp->path + 1;
myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS);
}
else {
myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
}
break;
case SSH_SFTP_CREATE_DIRS:
sshc->slash_pos = strchr(sshc->slash_pos, '/');
if(!sshp) {
result = CURLE_FAILED_INIT;
break;
}
else if(sshc->slash_pos) {
*sshc->slash_pos = 0;
infof(data, "Creating directory '%s'", sshp->path);
myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS_MKDIR);
break;
}
myssh_to(data, sshc, SSH_SFTP_UPLOAD_INIT);
break;
case SSH_SFTP_CREATE_DIRS_MKDIR:
if(!sshp) {
result = CURLE_FAILED_INIT;
break;
}
rc = sftp_mkdir(sshc->sftp_session, sshp->path,
(mode_t)data->set.new_directory_perms);
*sshc->slash_pos = '/';
++sshc->slash_pos;
if(rc < 0) {
err = sftp_get_error(sshc->sftp_session);
if((err != SSH_FX_FILE_ALREADY_EXISTS) &&
(err != SSH_FX_FAILURE) &&
(err != SSH_FX_PERMISSION_DENIED)) {
rc = myssh_to_SFTP_CLOSE(data, sshc);
break;
}
rc = 0;
}
myssh_to(data, sshc, SSH_SFTP_CREATE_DIRS);
break;
case SSH_SFTP_READDIR_INIT:
rc = sshp ? myssh_in_SFTP_READDIR_INIT(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_READDIR:
rc = sshp ? myssh_in_SFTP_READDIR(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_READDIR_LINK:
rc = myssh_in_SFTP_READDIR_LINK(data, sshc);
break;
case SSH_SFTP_READDIR_BOTTOM:
rc = myssh_in_SFTP_READDIR_BOTTOM(data, sshc);
break;
case SSH_SFTP_READDIR_DONE:
rc = myssh_in_SFTP_READDIR_DONE(data, sshc);
break;
case SSH_SFTP_DOWNLOAD_INIT:
rc = sshp ? myssh_in_SFTP_DOWNLOAD_INIT(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_DOWNLOAD_STAT:
rc = myssh_in_SFTP_DOWNLOAD_STAT(data, sshc);
break;
case SSH_SFTP_CLOSE:
rc = sshp ? myssh_in_SFTP_CLOSE(data, sshc, sshp) :
CURLE_FAILED_INIT;
break;
case SSH_SFTP_SHUTDOWN:
rc = myssh_in_SFTP_SHUTDOWN(data, sshc);
break;
case SSH_SCP_TRANS_INIT:
if(!sshp) {
result = CURLE_FAILED_INIT;
break;
}
result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
if(result) {
sshc->actualcode = result;
myssh_to(data, sshc, SSH_STOP);
break;
}
ssh_set_blocking(sshc->ssh_session, 1);
if(data->state.upload) {
if(data->state.infilesize < 0) {
failf(data, "SCP requires a known file size for upload");
sshc->actualcode = CURLE_UPLOAD_FAILED;
rc = myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
break;
}
sshc->scp_session =
ssh_scp_new(sshc->ssh_session, SSH_SCP_WRITE, sshp->path);
myssh_to(data, sshc, SSH_SCP_UPLOAD_INIT);
}
else {
sshc->scp_session =
ssh_scp_new(sshc->ssh_session, SSH_SCP_READ, sshp->path);
myssh_to(data, sshc, SSH_SCP_DOWNLOAD_INIT);
}
if(!sshc->scp_session) {
err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
rc = myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
}
break;
case SSH_SCP_UPLOAD_INIT:
if(!sshp) {
result = CURLE_FAILED_INIT;
break;
}
rc = ssh_scp_init(sshc->scp_session);
if(rc != SSH_OK) {
err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
rc = myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
break;
}
rc = ssh_scp_push_file64(sshc->scp_session, sshp->path,
(uint64_t)data->state.infilesize,
(int)data->set.new_file_perms);
if(rc != SSH_OK) {
err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
rc = myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
break;
}
Curl_xfer_setup_send(data, FIRSTSOCKET);
data->conn->recv_idx = FIRSTSOCKET;
sshc->orig_waitfor = data->req.keepon;
myssh_to(data, sshc, SSH_STOP);
break;
case SSH_SCP_DOWNLOAD_INIT:
rc = ssh_scp_init(sshc->scp_session);
if(rc != SSH_OK) {
err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
rc = myssh_to_ERROR(data, sshc, CURLE_COULDNT_CONNECT);
break;
}
myssh_to(data, sshc, SSH_SCP_DOWNLOAD);
FALLTHROUGH();
case SSH_SCP_DOWNLOAD: {
curl_off_t bytecount;
rc = ssh_scp_pull_request(sshc->scp_session);
if(rc != SSH_SCP_REQUEST_NEWFILE) {
err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
rc = myssh_to_ERROR(data, sshc, CURLE_REMOTE_FILE_NOT_FOUND);
break;
}
bytecount = ssh_scp_request_get_size(sshc->scp_session);
data->req.maxdownload = (curl_off_t) bytecount;
Curl_xfer_setup_recv(data, FIRSTSOCKET, bytecount);
conn->send_idx = 0;
myssh_to(data, sshc, SSH_STOP);
break;
}
case SSH_SCP_DONE:
if(data->state.upload)
myssh_to(data, sshc, SSH_SCP_SEND_EOF);
else
myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
break;
case SSH_SCP_SEND_EOF:
if(sshc->scp_session) {
rc = ssh_scp_close(sshc->scp_session);
if(rc == SSH_AGAIN) {
break;
}
if(rc != SSH_OK) {
infof(data, "Failed to close libssh scp channel: %s",
ssh_get_error(sshc->ssh_session));
}
}
myssh_to(data, sshc, SSH_SCP_CHANNEL_FREE);
break;
case SSH_SCP_CHANNEL_FREE:
if(sshc->scp_session) {
ssh_scp_free(sshc->scp_session);
sshc->scp_session = NULL;
}
DEBUGF(infof(data, "SCP DONE phase complete"));
ssh_set_blocking(sshc->ssh_session, 0);
myssh_to(data, sshc, SSH_SESSION_DISCONNECT);
FALLTHROUGH();
case SSH_SESSION_DISCONNECT:
if(sshc->scp_session) {
ssh_scp_free(sshc->scp_session);
sshc->scp_session = NULL;
}
if(sshc->sftp_file) {
sftp_close(sshc->sftp_file);
sshc->sftp_file = NULL;
}
if(sshc->sftp_session) {
sftp_free(sshc->sftp_session);
sshc->sftp_session = NULL;
}
ssh_disconnect(sshc->ssh_session);
if(!ssh_version(SSH_VERSION_INT(0, 10, 0))) {
Curl_conn_forget_socket(data, FIRSTSOCKET);
}
SSH_STRING_FREE_CHAR(sshc->homedir);
myssh_to(data, sshc, SSH_SESSION_FREE);
FALLTHROUGH();
case SSH_SESSION_FREE:
sshc_cleanup(sshc);
result = sshc->actualcode;
memset(sshc, 0, sizeof(struct ssh_conn));
connclose(conn, "SSH session free");
sshc->state = SSH_SESSION_FREE;
sshc->nextstate = SSH_NO_STATE;
myssh_to(data, sshc, SSH_STOP);
break;
case SSH_QUIT:
default:
sshc->nextstate = SSH_NO_STATE;
myssh_to(data, sshc, SSH_STOP);
break;
}
} while(!rc && (sshc->state != SSH_STOP));
if(rc == SSH_AGAIN) {
*block = TRUE;
}
if(!result && (sshc->state == SSH_STOP))
result = sshc->actualcode;
DEBUGF(infof(data, "SSH: myssh_statemach_act -> %d", result));
return result;
}
static CURLcode myssh_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;
if(!conn->waitfor)
flags |= CURL_POLL_OUT;
return flags ?
Curl_pollset_change(data, ps, conn->sock[FIRSTSOCKET], flags, 0) :
CURLE_OK;
}
static void myssh_block2waitfor(struct connectdata *conn,
struct ssh_conn *sshc,
bool block)
{
if(block) {
int dir = ssh_get_poll_flags(sshc->ssh_session);
conn->waitfor =
((dir & SSH_READ_PENDING) ? KEEP_RECV : 0) |
((dir & SSH_WRITE_PENDING) ? KEEP_SEND : 0);
}
else
conn->waitfor = sshc->orig_waitfor;
}
static CURLcode myssh_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);
bool block;
CURLcode result;
if(!sshc || !sshp)
return CURLE_FAILED_INIT;
result = myssh_statemach_act(data, sshc, sshp, &block);
*done = (sshc->state == SSH_STOP);
myssh_block2waitfor(conn, sshc, block);
return result;
}
static CURLcode myssh_block_statemach(struct Curl_easy *data,
struct ssh_conn *sshc,
struct SSHPROTO *sshp,
bool disconnect)
{
struct connectdata *conn = data->conn;
CURLcode result = CURLE_OK;
while((sshc->state != SSH_STOP) && !result) {
bool block;
timediff_t left = 1000;
struct curltime now = curlx_now();
result = myssh_statemach_act(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;
}
}
if(block) {
curl_socket_t fd_read = conn->sock[FIRSTSOCKET];
(void)Curl_socket_check(fd_read, CURL_SOCKET_BAD,
CURL_SOCKET_BAD, 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);
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);
free(sshc);
}
static CURLcode myssh_setup_connection(struct Curl_easy *data,
struct connectdata *conn)
{
struct SSHPROTO *sshp;
struct ssh_conn *sshc;
sshc = calloc(1, sizeof(*sshc));
if(!sshc)
return CURLE_OUT_OF_MEMORY;
curlx_dyn_init(&sshc->readdir_buf, CURL_PATH_MAX * 2);
sshc->initialised = TRUE;
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 ||
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;
static CURLcode myssh_connect(struct Curl_easy *data, bool *done)
{
CURLcode result;
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
struct SSHPROTO *ssh = Curl_meta_get(data, CURL_META_SSH_EASY);
curl_socket_t sock = conn->sock[FIRSTSOCKET];
int rc;
if(!sshc || !ssh)
return CURLE_FAILED_INIT;
connkeep(conn, "SSH default");
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;
}
sshc->ssh_session = ssh_new();
if(!sshc->ssh_session) {
failf(data, "Failure initialising ssh session");
return CURLE_FAILED_INIT;
}
if(conn->bits.ipv6_ip) {
char ipv6[MAX_IPADR_LEN];
curl_msnprintf(ipv6, sizeof(ipv6), "[%s]", conn->host.name);
rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, ipv6);
}
else
rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST, conn->host.name);
if(rc != SSH_OK) {
failf(data, "Could not set remote host");
return CURLE_FAILED_INIT;
}
rc = ssh_options_parse_config(sshc->ssh_session, NULL);
if(rc != SSH_OK) {
infof(data, "Could not parse SSH configuration files");
}
rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_FD, &sock);
if(rc != SSH_OK) {
failf(data, "Could not set socket");
return CURLE_FAILED_INIT;
}
if(conn->user && conn->user[0] != '\0') {
infof(data, "User: %s", conn->user);
rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_USER, conn->user);
if(rc != SSH_OK) {
failf(data, "Could not set user");
return CURLE_FAILED_INIT;
}
}
if(data->set.str[STRING_SSH_KNOWNHOSTS]) {
infof(data, "Known hosts: %s", data->set.str[STRING_SSH_KNOWNHOSTS]);
rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_KNOWNHOSTS,
data->set.str[STRING_SSH_KNOWNHOSTS]);
if(rc != SSH_OK) {
failf(data, "Could not set known hosts file path");
return CURLE_FAILED_INIT;
}
}
if(conn->remote_port) {
rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_PORT,
&conn->remote_port);
if(rc != SSH_OK) {
failf(data, "Could not set remote port");
return CURLE_FAILED_INIT;
}
}
if(data->set.ssh_compression) {
rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_COMPRESSION,
"zlib,zlib@openssh.com,none");
if(rc != SSH_OK) {
failf(data, "Could not set compression");
return CURLE_FAILED_INIT;
}
}
sshc->privkey = NULL;
sshc->pubkey = NULL;
if(data->set.str[STRING_SSH_PUBLIC_KEY]) {
rc = ssh_pki_import_pubkey_file(data->set.str[STRING_SSH_PUBLIC_KEY],
&sshc->pubkey);
if(rc != SSH_OK) {
failf(data, "Could not load public key file");
return CURLE_FAILED_INIT;
}
}
myssh_to(data, sshc, SSH_INIT);
result = myssh_multi_statemach(data, done);
return result;
}
static CURLcode scp_doing(struct Curl_easy *data, bool *dophase_done)
{
CURLcode result;
result = myssh_multi_statemach(data, dophase_done);
if(*dophase_done) {
DEBUGF(infof(data, "DO phase is complete"));
}
return result;
}
static
CURLcode scp_perform(struct Curl_easy *data,
bool *connected, bool *dophase_done)
{
CURLcode result = CURLE_OK;
struct ssh_conn *sshc = Curl_conn_meta_get(data->conn, CURL_META_SSH_CONN);
DEBUGF(infof(data, "DO phase starts"));
*dophase_done = FALSE;
if(!sshc)
return CURLE_FAILED_INIT;
myssh_to(data, sshc, SSH_SCP_TRANS_INIT);
result = myssh_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 myssh_do_it(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->actualcode = CURLE_OK;
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 void sshc_cleanup(struct ssh_conn *sshc)
{
if(sshc->initialised) {
if(sshc->sftp_file) {
sftp_close(sshc->sftp_file);
sshc->sftp_file = NULL;
}
if(sshc->sftp_session) {
sftp_free(sshc->sftp_session);
sshc->sftp_session = NULL;
}
if(sshc->ssh_session) {
ssh_free(sshc->ssh_session);
sshc->ssh_session = NULL;
}
DEBUGASSERT(sshc->ssh_session == NULL);
DEBUGASSERT(sshc->scp_session == NULL);
if(sshc->readdir_tmp) {
ssh_string_free_char(sshc->readdir_tmp);
sshc->readdir_tmp = NULL;
}
if(sshc->quote_attrs) {
sftp_attributes_free(sshc->quote_attrs);
sshc->quote_attrs = NULL;
}
if(sshc->readdir_attrs) {
sftp_attributes_free(sshc->readdir_attrs);
sshc->readdir_attrs = NULL;
}
if(sshc->readdir_link_attrs) {
sftp_attributes_free(sshc->readdir_link_attrs);
sshc->readdir_link_attrs = NULL;
}
if(sshc->privkey) {
ssh_key_free(sshc->privkey);
sshc->privkey = NULL;
}
if(sshc->pubkey) {
ssh_key_free(sshc->pubkey);
sshc->pubkey = NULL;
}
Curl_safefree(sshc->rsa_pub);
Curl_safefree(sshc->rsa);
Curl_safefree(sshc->quote_path1);
Curl_safefree(sshc->quote_path2);
curlx_dyn_free(&sshc->readdir_buf);
Curl_safefree(sshc->readdir_linkPath);
SSH_STRING_FREE_CHAR(sshc->homedir);
sshc->initialised = FALSE;
}
}
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_to(data, sshc, SSH_SESSION_DISCONNECT);
result = myssh_block_statemach(data, sshc, sshp, TRUE);
}
return result;
}
static CURLcode myssh_done(struct Curl_easy *data,
struct ssh_conn *sshc,
CURLcode status)
{
CURLcode result = CURLE_OK;
struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY);
if(!status && sshp) {
result = myssh_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)
return CURLE_FAILED_INIT;
if(!status)
myssh_to(data, sshc, SSH_SCP_DONE);
return myssh_done(data, sshc, status);
}
static CURLcode scp_send(struct Curl_easy *data, int sockindex,
const void *mem, size_t len, bool eos,
size_t *pnwritten)
{
int rc;
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
(void)sockindex;
(void)eos;
*pnwritten = 0;
if(!sshc)
return CURLE_FAILED_INIT;
rc = ssh_scp_write(sshc->scp_session, mem, len);
#if 0#endif
if(rc != SSH_OK)
return CURLE_SSH;
*pnwritten = len;
return CURLE_OK;
}
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);
int nread;
(void)sockindex;
*pnread = 0;
if(!sshc)
return CURLE_FAILED_INIT;
nread = ssh_scp_read(sshc->scp_session, mem, len);
if(nread == SSH_ERROR)
return CURLE_SSH;
#if 0#endif
*pnread = (size_t)nread;
return CURLE_OK;
}
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_to(data, sshc, SSH_SFTP_QUOTE_INIT);
result = myssh_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 = myssh_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)
{
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;
(void)dead_connection;
DEBUGF(infof(data, "SSH DISCONNECT starts now"));
if(sshc && sshc->ssh_session) {
myssh_to(data, sshc, SSH_SFTP_SHUTDOWN);
result = myssh_block_statemach(data, sshc, sshp, TRUE);
}
DEBUGF(infof(data, "SSH DISCONNECT is done"));
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_to(data, sshc, SSH_SFTP_CLOSE);
}
return myssh_done(data, sshc, 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;
#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0)
switch(sshc->sftp_send_state) {
case 0:
sftp_file_set_nonblocking(sshc->sftp_file);
if(sftp_aio_begin_write(sshc->sftp_file, mem, len,
&sshc->sftp_send_aio) == SSH_ERROR) {
return CURLE_SEND_ERROR;
}
sshc->sftp_send_state = 1;
FALLTHROUGH();
case 1:
nwrite = sftp_aio_wait_write(&sshc->sftp_send_aio);
myssh_block2waitfor(conn, sshc, (nwrite == SSH_AGAIN) ? TRUE : FALSE);
if(nwrite == SSH_AGAIN)
return CURLE_AGAIN;
else if(nwrite < 0)
return CURLE_SEND_ERROR;
sshc->sftp_send_state = 0;
*pnwritten = (size_t)nwrite;
return CURLE_OK;
default:
return CURLE_SEND_ERROR;
}
#else
if(len > 32768)
len = 32768;
nwrite = sftp_write(sshc->sftp_file, mem, len);
myssh_block2waitfor(conn, sshc, FALSE);
#if 0#endif
if(nwrite < 0)
return CURLE_SSH;
*pnwritten = (size_t)nwrite;
return CURLE_OK;
#endif
}
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;
DEBUGASSERT(len < CURL_MAX_READ_SIZE);
if(!sshc)
return CURLE_FAILED_INIT;
switch(sshc->sftp_recv_state) {
case 0:
#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0)
if(sftp_aio_begin_read(sshc->sftp_file, len,
&sshc->sftp_recv_aio) == SSH_ERROR) {
return CURLE_RECV_ERROR;
}
#else
sshc->sftp_file_index =
sftp_async_read_begin(sshc->sftp_file, (uint32_t)len);
if(sshc->sftp_file_index < 0)
return CURLE_RECV_ERROR;
#endif
FALLTHROUGH();
case 1:
sshc->sftp_recv_state = 1;
#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0)
nread = sftp_aio_wait_read(&sshc->sftp_recv_aio, mem, len);
#else
nread = sftp_async_read(sshc->sftp_file, mem, (uint32_t)len,
(uint32_t)sshc->sftp_file_index);
#endif
myssh_block2waitfor(conn, sshc, (nread == SSH_AGAIN));
if(nread == SSH_AGAIN)
return CURLE_AGAIN;
else if(nread < 0)
return CURLE_RECV_ERROR;
sshc->sftp_recv_state = 0;
*pnread = (size_t)nread;
return CURLE_OK;
default:
return CURLE_RECV_ERROR;
}
}
CURLcode Curl_ssh_init(void)
{
if(ssh_init()) {
DEBUGF(curl_mfprintf(stderr, "Error: libssh_init failed\n"));
return CURLE_FAILED_INIT;
}
return CURLE_OK;
}
void Curl_ssh_cleanup(void)
{
(void)ssh_finalize();
}
void Curl_ssh_version(char *buffer, size_t buflen)
{
(void)curl_msnprintf(buffer, buflen, "libssh/%s", ssh_version(0));
}
#endif