libssh: split up the state machine function

This reduces the "complexity score" for myssh_statemach_act from 160 to
100, taking it down from the most complex function in libcurl to the
5th.

Also fixes a memory leak of the sftp session.

Closes #17346
This commit is contained in:
Daniel Stenberg 2025-05-14 11:46:11 +02:00
parent 028c12948c
commit 6f6ee601b9
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

View file

@ -627,6 +627,451 @@ restart:
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
ssh_set_log_level(SSH_LOG_PROTOCOL);
#endif
/* Set libssh to non-blocking, since everything internally is
non-blocking */
ssh_set_blocking(sshc->ssh_session, 0);
myssh_state(data, sshc, SSH_S_STARTUP);
}
static int myssh_state_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");
MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT);
}
else
myssh_state(data, sshc, SSH_HOSTKEY);
return rc;
}
static int myssh_state_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_state(data, sshc, SSH_AUTH_DONE);
return rc;
}
else if(rc == SSH_AUTH_ERROR) {
MOVE_TO_ERROR_STATE(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_state(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_state(data, sshc, SSH_AUTH_GSSAPI);
}
else if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) {
myssh_state(data, sshc, SSH_AUTH_KEY_INIT);
}
else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) {
myssh_state(data, sshc, SSH_AUTH_PASS_INIT);
}
else { /* unsupported authentication method */
MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
}
return rc;
}
static int myssh_state_auth_pkey_init(struct Curl_easy *data,
struct ssh_conn *sshc)
{
int rc;
if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) {
MOVE_TO_GSSAPI_AUTH;
return 0;
}
/* Two choices, (1) private key was given on CMD,
* (2) use the "default" keys. */
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) {
MOVE_TO_GSSAPI_AUTH;
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]);
MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
return rc;
}
myssh_state(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_state(data, sshc, SSH_AUTH_DONE);
return rc;
}
MOVE_TO_GSSAPI_AUTH;
}
return rc;
}
static int myssh_state_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);
MOVE_TO_ERROR_STATE(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)
/* Try to open for append, but create if nonexisting */
flags = O_WRONLY|O_CREAT|O_APPEND;
else if(data->state.resume_from > 0)
/* If we have restart position then open for append */
flags = O_WRONLY|O_APPEND;
else
/* Clear file before writing (normal behavior) */
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))) {
/* try to create the path remotely */
rc = 0;
sshc->secondCreateDirs = 1;
myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_INIT);
return rc;
}
else {
MOVE_TO_SFTP_CLOSE_STATE();
return rc;
}
}
/* If we have a restart point then we need to seek to the correct
position. */
if(data->state.resume_from > 0) {
int seekerr = CURL_SEEKFUNC_OK;
/* Let's read off the proper amount of bytes from the input. */
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");
MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST);
return rc;
}
/* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */
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)) {
/* this checks for greater-than only to make sure that the
CURL_READFUNC_ABORT return code still aborts */
failf(data, "Failed to read data");
MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST);
return rc;
}
} while(passed < data->state.resume_from);
if(rc)
return rc;
}
/* now, decrease the size of the read */
if(data->state.infilesize > 0) {
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) {
MOVE_TO_SFTP_CLOSE_STATE();
return rc;
}
}
if(data->state.infilesize > 0) {
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->state.infilesize);
}
/* upload data */
Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE);
/* not set by Curl_xfer_setup to preserve keepon bits */
data->conn->sockfd = data->conn->writesockfd;
/* store this original bitmask setup to use later on if we cannot
figure out a "real" bitmask */
sshc->orig_waitfor = data->req.keepon;
/* we want to use the _sending_ function even when the socket turns
out readable as the underlying libssh sftp send function will deal
with both accordingly */
data->state.select_bits = CURL_CSELECT_OUT;
/* since we do not really wait for anything at this point, we want the
state machine to move on as soon as possible so we set a very short
timeout here */
Curl_expire(data, 0, EXPIRE_RUN_NOW);
#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0)
sshc->sftp_send_state = 0;
#endif
myssh_state(data, sshc, SSH_STOP);
return rc;
}
static int myssh_state_sftp_dowload_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)) {
/*
* sftp_fstat did not return an error, so maybe the server
* just does not support stat()
* OR the server does not return a file size with a stat()
* OR file size is 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);
MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME);
return rc;
}
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) {
MOVE_TO_ERROR_STATE(CURLE_RANGE_ERROR);
return rc;
}
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;
to_t = STRE_OK;
}
if(from_t == STRE_NO_NUM) {
/* from is relative to end of file */
from = size - to;
to = size - 1;
from_t = STRE_OK;
}
if(from > size) {
failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%"
FMT_OFF_T ")", from, size);
MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME);
return rc;
}
if(from > to) {
from = to;
size = 0;
}
else {
if((to - from) == CURL_OFF_T_MAX) {
MOVE_TO_ERROR_STATE(CURLE_RANGE_ERROR);
return rc;
}
size = to - from + 1;
}
rc = sftp_seek64(sshc->sftp_file, from);
if(rc) {
MOVE_TO_SFTP_CLOSE_STATE();
return rc;
}
}
data->req.size = size;
data->req.maxdownload = size;
Curl_pgrsSetDownloadSize(data, size);
}
/* We can resume if we can seek to the resume position */
if(data->state.resume_from) {
if(data->state.resume_from < 0) {
/* We are supposed to download the last abs(from) bytes */
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);
MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME);
return rc;
}
/* download from where? */
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);
MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME);
return rc;
}
}
/* Now store the number of bytes we are expected to download */
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) {
MOVE_TO_SFTP_CLOSE_STATE();
return rc;
}
}
/* Setup the actual download */
if(data->req.size == 0) {
/* no data to transfer */
Curl_xfer_setup_nop(data);
infof(data, "File already completely downloaded");
myssh_state(data, sshc, SSH_STOP);
return rc;
}
Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE);
/* not set by Curl_xfer_setup to preserve keepon bits */
data->conn->writesockfd = data->conn->sockfd;
/* we want to use the _receiving_ function even when the socket turns
out writableable as the underlying libssh recv function will deal
with both accordingly */
data->state.select_bits = CURL_CSELECT_IN;
sshc->sftp_recv_state = 0;
myssh_state(data, sshc, SSH_STOP);
return rc;
}
/*
* ssh_statemach_act() runs the SSH state machine as far as it can without
* blocking and without reaching the end. The data the pointer 'block' points
@ -642,7 +1087,6 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
struct connectdata *conn = data->conn;
curl_socket_t sock = conn->sock[FIRSTSOCKET];
int rc = SSH_NO_ERROR, err;
int seekerr = CURL_SEEKFUNC_OK;
const char *err_msg;
*block = 0; /* we are not blocking by default */
@ -650,41 +1094,15 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
switch(sshc->state) {
case SSH_INIT:
sshc->secondCreateDirs = 0;
sshc->nextstate = SSH_NO_STATE;
sshc->actualcode = CURLE_OK;
#if 0
ssh_set_log_level(SSH_LOG_PROTOCOL);
#endif
/* Set libssh to non-blocking, since everything internally is
non-blocking */
ssh_set_blocking(sshc->ssh_session, 0);
myssh_state(data, sshc, SSH_S_STARTUP);
myssh_state_init(data, sshc);
FALLTHROUGH();
case SSH_S_STARTUP:
rc = ssh_connect(sshc->ssh_session);
myssh_block2waitfor(conn, sshc, (rc == SSH_AGAIN));
if(rc == SSH_AGAIN) {
DEBUGF(infof(data, "ssh_connect -> EAGAIN"));
rc = myssh_state_startup(data, sshc);
if(rc)
break;
}
if(rc != SSH_OK) {
failf(data, "Failure establishing ssh session");
MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT);
break;
}
myssh_state(data, sshc, SSH_HOSTKEY);
FALLTHROUGH();
case SSH_HOSTKEY:
rc = myssh_is_known(data, sshc);
if(rc != SSH_OK) {
MOVE_TO_ERROR_STATE(CURLE_PEER_FAILED_VERIFICATION);
@ -693,113 +1111,11 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
myssh_state(data, sshc, SSH_AUTHLIST);
FALLTHROUGH();
case SSH_AUTHLIST:{
sshc->authed = FALSE;
rc = ssh_userauth_none(sshc->ssh_session, NULL);
if(rc == SSH_AUTH_AGAIN) {
rc = SSH_AGAIN;
break;
}
if(rc == SSH_AUTH_SUCCESS) {
sshc->authed = TRUE;
infof(data, "Authenticated with none");
myssh_state(data, sshc, SSH_AUTH_DONE);
break;
}
else if(rc == SSH_AUTH_ERROR) {
MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
break;
}
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_state(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_state(data, sshc, SSH_AUTH_GSSAPI);
}
else if(sshc->auth_methods & SSH_AUTH_METHOD_INTERACTIVE) {
myssh_state(data, sshc, SSH_AUTH_KEY_INIT);
}
else if(sshc->auth_methods & SSH_AUTH_METHOD_PASSWORD) {
myssh_state(data, sshc, SSH_AUTH_PASS_INIT);
}
else { /* unsupported authentication method */
MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
break;
}
break;
}
case SSH_AUTHLIST:
rc = myssh_state_authlist(data, sshc);
break;
case SSH_AUTH_PKEY_INIT:
if(!(data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY)) {
MOVE_TO_GSSAPI_AUTH;
break;
}
/* Two choices, (1) private key was given on CMD,
* (2) use the "default" keys. */
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) {
rc = SSH_AGAIN;
break;
}
if(rc != SSH_OK) {
MOVE_TO_GSSAPI_AUTH;
break;
}
}
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]);
MOVE_TO_ERROR_STATE(CURLE_LOGIN_DENIED);
break;
}
myssh_state(data, sshc, SSH_AUTH_PKEY);
break;
}
else {
rc = ssh_userauth_publickey_auto(sshc->ssh_session, NULL,
data->set.ssl.key_passwd);
if(rc == SSH_AUTH_AGAIN) {
rc = SSH_AGAIN;
break;
}
if(rc == SSH_AUTH_SUCCESS) {
rc = SSH_OK;
sshc->authed = TRUE;
infof(data, "Completed public key authentication");
myssh_state(data, sshc, SSH_AUTH_DONE);
break;
}
MOVE_TO_GSSAPI_AUTH;
}
rc = myssh_state_auth_pkey_init(data, sshc);
break;
case SSH_AUTH_PKEY:
rc = ssh_userauth_publickey(sshc->ssh_session, NULL, sshc->privkey);
@ -1206,150 +1522,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
break;
case SSH_SFTP_UPLOAD_INIT:
{
int flags;
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);
MOVE_TO_ERROR_STATE(CURLE_BAD_DOWNLOAD_RESUME);
break;
}
data->state.resume_from = attrs->size;
sftp_attributes_free(attrs);
}
else {
data->state.resume_from = 0;
}
}
}
if(data->set.remote_append)
/* Try to open for append, but create if nonexisting */
flags = O_WRONLY|O_CREAT|O_APPEND;
else if(data->state.resume_from > 0)
/* If we have restart position then open for append */
flags = O_WRONLY|O_APPEND;
else
/* Clear file before writing (normal behavior) */
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) {
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))) {
/* try to create the path remotely */
rc = 0;
sshc->secondCreateDirs = 1;
myssh_state(data, sshc, SSH_SFTP_CREATE_DIRS_INIT);
break;
}
else {
MOVE_TO_SFTP_CLOSE_STATE();
break;
}
}
/* If we have a restart point then we need to seek to the correct
position. */
if(data->state.resume_from > 0) {
/* Let's read off the proper amount of bytes from the input. */
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;
}
/* seekerr == CURL_SEEKFUNC_CANTSEEK (cannot seek to offset) */
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)) {
/* this checks for greater-than only to make sure that the
CURL_READFUNC_ABORT return code still aborts */
failf(data, "Failed to read data");
MOVE_TO_ERROR_STATE(CURLE_FTP_COULDNT_USE_REST);
break;
}
} while(passed < data->state.resume_from);
if(rc)
break;
}
/* now, decrease the size of the read */
if(data->state.infilesize > 0) {
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) {
MOVE_TO_SFTP_CLOSE_STATE();
break;
}
}
if(data->state.infilesize > 0) {
data->req.size = data->state.infilesize;
Curl_pgrsSetUploadSize(data, data->state.infilesize);
}
/* upload data */
Curl_xfer_setup1(data, CURL_XFER_SEND, -1, FALSE);
/* not set by Curl_xfer_setup to preserve keepon bits */
conn->sockfd = conn->writesockfd;
/* store this original bitmask setup to use later on if we cannot
figure out a "real" bitmask */
sshc->orig_waitfor = data->req.keepon;
/* we want to use the _sending_ function even when the socket turns
out readable as the underlying libssh sftp send function will deal
with both accordingly */
data->state.select_bits = CURL_CSELECT_OUT;
/* since we do not really wait for anything at this point, we want the
state machine to move on as soon as possible so we set a very short
timeout here */
Curl_expire(data, 0, EXPIRE_RUN_NOW);
#if LIBSSH_VERSION_INT > SSH_VERSION_INT(0, 11, 0)
sshc->sftp_send_state = 0;
#endif
myssh_state(data, sshc, SSH_STOP);
rc = myssh_state_upload_init(data, sshc, sshp);
break;
}
case SSH_SFTP_CREATE_DIRS_INIT:
if(strlen(sshp->path) > 1) {
@ -1579,149 +1753,8 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
break;
case SSH_SFTP_DOWNLOAD_STAT:
{
sftp_attributes attrs;
curl_off_t size;
attrs = sftp_fstat(sshc->sftp_file);
if(!attrs ||
!(attrs->flags & SSH_FILEXFER_ATTR_SIZE) ||
(attrs->size == 0)) {
/*
* sftp_fstat did not return an error, so maybe the server
* just does not support stat()
* OR the server does not return a file size with a stat()
* OR file size is 0
*/
data->req.size = -1;
data->req.maxdownload = -1;
Curl_pgrsSetDownloadSize(data, -1);
size = 0;
}
else {
size = attrs->size;
sftp_attributes_free(attrs);
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 from_t, to_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;
to_t = STRE_OK;
}
if(from_t == STRE_NO_NUM) {
/* from is relative to end of file */
from = size - to;
to = size - 1;
from_t = STRE_OK;
}
if(from > size) {
failf(data, "Offset (%" FMT_OFF_T ") was beyond file size (%"
FMT_OFF_T ")", from, size);
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;
}
rc = sftp_seek64(sshc->sftp_file, from);
if(rc) {
MOVE_TO_SFTP_CLOSE_STATE();
break;
}
}
data->req.size = size;
data->req.maxdownload = size;
Curl_pgrsSetDownloadSize(data, size);
}
/* We can resume if we can seek to the resume position */
if(data->state.resume_from) {
if(data->state.resume_from < 0) {
/* We are supposed to download the last abs(from) bytes */
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 CURLE_BAD_DOWNLOAD_RESUME;
}
/* download from where? */
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 CURLE_BAD_DOWNLOAD_RESUME;
}
}
/* Now store the number of bytes we are expected to download */
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) {
MOVE_TO_SFTP_CLOSE_STATE();
break;
}
}
}
/* Setup the actual download */
if(data->req.size == 0) {
/* no data to transfer */
Curl_xfer_setup_nop(data);
infof(data, "File already completely downloaded");
myssh_state(data, sshc, SSH_STOP);
rc = myssh_state_sftp_dowload_stat(data, sshc);
break;
}
Curl_xfer_setup1(data, CURL_XFER_RECV, data->req.size, FALSE);
/* not set by Curl_xfer_setup to preserve keepon bits */
conn->writesockfd = conn->sockfd;
/* we want to use the _receiving_ function even when the socket turns
out writableable as the underlying libssh recv function will deal
with both accordingly */
data->state.select_bits = CURL_CSELECT_IN;
if(result) {
/* this should never occur; the close state should be entered
at the time the error occurs */
myssh_state(data, sshc, SSH_SFTP_CLOSE);
sshc->actualcode = result;
}
else {
sshc->sftp_recv_state = 0;
myssh_state(data, sshc, SSH_STOP);
}
break;
case SSH_SFTP_CLOSE:
if(sshc->sftp_file) {
@ -1935,6 +1968,15 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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))) {
/* conn->sock[FIRSTSOCKET] is closed by ssh_disconnect behind our back,
@ -2333,6 +2375,14 @@ static CURLcode myssh_do_it(struct Curl_easy *data, bool *done)
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;