From 6f6ee601b9998b1507fff665aeef2b8d0e6d887b Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Wed, 14 May 2025 11:46:11 +0200 Subject: [PATCH] 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 --- lib/vssh/libssh.c | 892 ++++++++++++++++++++++++---------------------- 1 file changed, 471 insertions(+), 421 deletions(-) diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c index e288cd2ec5..6eac9d697b 100644 --- a/lib/vssh/libssh.c +++ b/lib/vssh/libssh.c @@ -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")); - break; - } - - if(rc != SSH_OK) { - failf(data, "Failure establishing ssh session"); - MOVE_TO_ERROR_STATE(CURLE_FAILED_INIT); + rc = myssh_state_startup(data, sshc); + if(rc) 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; -- 2.47.3