libssh: simplify myssh_statemach_act()

Reduce complexity by splitting out sub functions.

Made myssh_quote_error() to reduce code duplication.

Closes #20252
This commit is contained in:
Daniel Stenberg 2026-01-11 11:15:00 +01:00
parent 1b24428d62
commit bd3b2a626a
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2

View File

@ -727,6 +727,19 @@ static int myssh_in_SFTP_READDIR_DONE(struct Curl_easy *data,
return SSH_NO_ERROR;
}
static void myssh_quote_error(struct Curl_easy *data, struct ssh_conn *sshc,
const char *cmd)
{
if(cmd)
failf(data, "%s command failed: %s", cmd,
ssh_get_error(sshc->ssh_session));
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;
}
static int myssh_in_SFTP_QUOTE_STATVFS(struct Curl_easy *data,
struct ssh_conn *sshc)
{
@ -734,12 +747,7 @@ static int myssh_in_SFTP_QUOTE_STATVFS(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, "statvfs");
return SSH_OK;
}
else if(statvfs) {
@ -1765,13 +1773,9 @@ static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, NULL);
return SSH_NO_ERROR;
}
@ -1780,12 +1784,8 @@ static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, NULL);
return SSH_NO_ERROR;
}
sshc->quote_attrs->gid = (uint32_t)gid;
@ -1795,12 +1795,8 @@ static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, NULL);
return SSH_NO_ERROR;
}
sshc->quote_attrs->permissions = (mode_t)perms;
@ -1810,12 +1806,8 @@ static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, NULL);
return SSH_NO_ERROR;
}
sshc->quote_attrs->uid = (uint32_t)uid;
@ -1836,11 +1828,7 @@ static int myssh_in_SFTP_QUOTE_STAT(struct Curl_easy *data,
}
#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;
myssh_quote_error(data, sshc, NULL);
return SSH_NO_ERROR;
}
if(date > UINT_MAX)
@ -1871,6 +1859,104 @@ static void conn_forget_socket(struct Curl_easy *data, int sockindex)
}
}
static void myssh_SESSION_DISCONNECT(struct Curl_easy *data,
struct ssh_conn *sshc)
{
/* during weird times when we have been prematurely aborted, the channel
is still alive when we reach this state and we MUST kill the channel
properly first */
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))) {
/* conn->sock[FIRSTSOCKET] is closed by ssh_disconnect behind our back,
tell the connection to forget about it. This libssh
bug is fixed in 0.10.0. */
conn_forget_socket(data, FIRSTSOCKET);
}
SSH_STRING_FREE_CHAR(sshc->homedir);
myssh_to(data, sshc, SSH_SESSION_FREE);
}
static int myssh_SSH_SCP_DOWNLOAD(struct Curl_easy *data,
struct ssh_conn *sshc)
{
curl_off_t bytecount;
int rc = ssh_scp_pull_request(sshc->scp_session);
if(rc != SSH_SCP_REQUEST_NEWFILE) {
const char *err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
return myssh_to_ERROR(data, sshc, CURLE_REMOTE_FILE_NOT_FOUND);
}
/* download data */
bytecount = ssh_scp_request_get_size(sshc->scp_session);
data->req.maxdownload = (curl_off_t)bytecount;
Curl_xfer_setup_recv(data, FIRSTSOCKET, bytecount);
/* not set by Curl_xfer_setup to preserve keepon bits */
data->conn->send_idx = 0;
myssh_to(data, sshc, SSH_STOP);
return 0;
}
static int myssh_in_TRANS_INIT(struct Curl_easy *data, struct ssh_conn *sshc,
struct SSHPROTO *sshp)
{
CURLcode result;
int rc = 0;
if(!sshp)
return myssh_to_ERROR(data, sshc, CURLE_FAILED_INIT);
result = Curl_getworkingpath(data, sshc->homedir, &sshp->path);
if(result) {
sshc->actualcode = result;
myssh_to(data, sshc, SSH_STOP);
return 0;
}
/* Functions from the SCP subsystem cannot handle/return SSH_AGAIN */
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");
return myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
}
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) {
const char *err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
rc = myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
}
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
@ -1883,9 +1969,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
bool *block)
{
CURLcode result = CURLE_OK;
struct connectdata *conn = data->conn;
int rc = SSH_NO_ERROR, err;
const char *err_msg;
*block = FALSE; /* we are not blocking by default */
do {
@ -1948,8 +2032,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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;
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);
@ -1957,23 +2040,13 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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;
/* sshc->actualcode = sftp_error_to_CURLE(err);
* we do not send the actual error; we return
* the error the libssh2 backend is returning */
myssh_quote_error(data, sshc, "setstat");
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
@ -1985,13 +2058,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, "symlink");
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
@ -2003,12 +2070,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, "mkdir");
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
@ -2020,13 +2082,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, "rename");
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
@ -2037,12 +2093,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, "rmdir");
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
@ -2053,11 +2104,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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;
myssh_quote_error(data, sshc, "rm");
break;
}
myssh_to(data, sshc, SSH_SFTP_NEXT_QUOTE);
@ -2173,8 +2220,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
CURLE_FAILED_INIT;
break;
case SSH_SFTP_READDIR:
rc = sshp ? myssh_in_SFTP_READDIR(data, sshc, sshp) :
CURLE_FAILED_INIT;
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);
@ -2193,51 +2239,13 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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;
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;
}
/* Functions from the SCP subsystem cannot handle/return SSH_AGAIN */
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);
}
rc = myssh_in_TRANS_INIT(data, sshc, sshp);
break;
case SSH_SCP_UPLOAD_INIT:
@ -2247,7 +2255,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
}
rc = ssh_scp_init(sshc->scp_session);
if(rc != SSH_OK) {
err_msg = ssh_get_error(sshc->ssh_session);
const char *err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
rc = myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
break;
@ -2258,7 +2266,7 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
(int)data->set.new_file_perms);
if(rc != SSH_OK) {
err_msg = ssh_get_error(sshc->ssh_session);
const char *err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
rc = myssh_to_ERROR(data, sshc, CURLE_UPLOAD_FAILED);
break;
@ -2275,10 +2283,9 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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);
const char *err_msg = ssh_get_error(sshc->ssh_session);
failf(data, "%s", err_msg);
rc = myssh_to_ERROR(data, sshc, CURLE_COULDNT_CONNECT);
break;
@ -2286,28 +2293,10 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
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);
case SSH_SCP_DOWNLOAD:
rc = myssh_SSH_SCP_DOWNLOAD(data, sshc);
break;
}
/* download data */
bytecount = ssh_scp_request_get_size(sshc->scp_session);
data->req.maxdownload = (curl_off_t)bytecount;
Curl_xfer_setup_recv(data, FIRSTSOCKET, bytecount);
/* not set by Curl_xfer_setup to preserve keepon bits */
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);
@ -2346,41 +2335,14 @@ static CURLcode myssh_statemach_act(struct Curl_easy *data,
FALLTHROUGH();
case SSH_SESSION_DISCONNECT:
/* during weird times when we have been prematurely aborted, the channel
is still alive when we reach this state and we MUST kill the channel
properly first */
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))) {
/* conn->sock[FIRSTSOCKET] is closed by ssh_disconnect behind our back,
tell the connection to forget about it. This libssh
bug is fixed in 0.10.0. */
conn_forget_socket(data, FIRSTSOCKET);
}
SSH_STRING_FREE_CHAR(sshc->homedir);
myssh_to(data, sshc, SSH_SESSION_FREE);
myssh_SESSION_DISCONNECT(data, sshc);
FALLTHROUGH();
case SSH_SESSION_FREE:
sshc_cleanup(sshc);
/* the code we are about to return */
result = sshc->actualcode;
memset(sshc, 0, sizeof(struct ssh_conn));
connclose(conn, "SSH session free");
connclose(data->conn, "SSH session free");
sshc->state = SSH_SESSION_FREE; /* current */
sshc->nextstate = SSH_NO_STATE;
myssh_to(data, sshc, SSH_STOP);