From e90eed8b8f4210dd09956d8e143726e8dd165397 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Wed, 2 Sep 2026 09:42:42 -0700 Subject: [PATCH 1/2] SCP: let the application start the transfer An application that binds an "scp ..." command to a channel itself has no way to run the transfer; wolfSSH_accept() did it through a WS_SCP_INIT re-entry only that state machine can drive. - add wolfSSH_SCP_accept(), a wrapper over DoScpRequest() reporting WS_SCP_COMPLETE for any non-negative result, as accept() does - a receive-side want reaches the wrapper as a generic error with the want in ssh->error, so report the want itself and let the caller retry - state that resume contract beside the prototype, and clear a stale want on entry the way the other re-entrant entry points do --- src/wolfscp.c | 34 ++++++++++++++++++++++++++++++++++ wolfssh/wolfscp.h | 11 +++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/wolfscp.c b/src/wolfscp.c index 184d057cf..e5d95a3ea 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -883,6 +883,40 @@ int DoScpSource(WOLFSSH* ssh) return ret; } +/* Contract is in wolfssh/wolfscp.h. */ +int wolfSSH_SCP_accept(WOLFSSH* ssh) +{ + int ret; + + if (ssh == NULL) + return WS_BAD_ARGUMENT; + + /* Clear a want left by the previous call so the retry starts clean, + * the way the other re-entrant entry points do. */ + if (ssh->error == WS_WANT_READ || ssh->error == WS_WANT_WRITE) + ssh->error = WS_SUCCESS; + + ret = DoScpRequest(ssh); + + if (ret >= WS_SUCCESS) { + /* The tail of DoScpRequest() passes a read count through, so treat + * anything non-negative as done the way wolfSSH_accept() does. */ + ret = WS_SCP_COMPLETE; + } + else { + /* A non-blocking want on a read path surfaces as a generic error + * with the want recorded in ssh->error (see GetInputData), so + * report it as the want the caller is told to retry on. */ + int err = wolfSSH_get_error(ssh); + + if (err == WS_WANT_READ || err == WS_WANT_WRITE) + ret = err; + } + + return ret; +} + + int DoScpRequest(WOLFSSH* ssh) { int ret = WS_SUCCESS; diff --git a/wolfssh/wolfscp.h b/wolfssh/wolfscp.h index 32e9dc7de..1ebeb339f 100644 --- a/wolfssh/wolfscp.h +++ b/wolfssh/wolfscp.h @@ -158,6 +158,17 @@ WOLFSSH_API int wolfSSH_SCP_to(WOLFSSH* ssh, const char* src, const char* dst); WOLFSSH_API int wolfSSH_SCP_from(WOLFSSH* ssh, const char* src, const char* dst); +/* Server side. Drives an SCP transfer on a channel whose "exec scp ..." + * command is already bound. This is the same work wolfSSH_accept() does + * through its WS_SCP_INIT re-entry, exposed so an application can start the + * transfer itself; use one or the other, not both. Call it once + * wolfSSH_accept() has returned and the exec channel-request callback has + * reported an SCP command, not from inside that callback. + * + * Returns WS_SCP_COMPLETE when the transfer is done. On a non-blocking + * socket it returns WS_WANT_READ or WS_WANT_WRITE with the transfer part + * done; call it again on the same session until it completes. */ +WOLFSSH_API int wolfSSH_SCP_accept(WOLFSSH* ssh); #ifdef __cplusplus From d93ce2eacc0871a5f3ce6d336433db4e9f07c9ad Mon Sep 17 00:00:00 2001 From: John Safranek Date: Fri, 4 Sep 2026 16:41:08 -0700 Subject: [PATCH 2/2] SCP: report only a read want from SCP_accept wolfSSH_SCP_accept() reported any want held in ssh->error in place of the DoScpRequest() result. A short send that SendChannelData() accepts leaves WS_WANT_WRITE there with nothing clearing it on a later flush, so a terminal result came back as retryable and the retry re-entered the state machine, re-sending an abort confirmation or reading past a bad message. - Substitute the want only when the result is WS_FATAL_ERROR and the want is WS_WANT_READ, the one case GetInputData() hides by value. - Leave write wants alone; the SCP state machine already returns them. --- src/wolfscp.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/wolfscp.c b/src/wolfscp.c index e5d95a3ea..c247eacc4 100644 --- a/src/wolfscp.c +++ b/src/wolfscp.c @@ -903,14 +903,11 @@ int wolfSSH_SCP_accept(WOLFSSH* ssh) * anything non-negative as done the way wolfSSH_accept() does. */ ret = WS_SCP_COMPLETE; } - else { - /* A non-blocking want on a read path surfaces as a generic error - * with the want recorded in ssh->error (see GetInputData), so - * report it as the want the caller is told to retry on. */ - int err = wolfSSH_get_error(ssh); - - if (err == WS_WANT_READ || err == WS_WANT_WRITE) - ret = err; + else if (ret == WS_FATAL_ERROR && wolfSSH_get_error(ssh) == WS_WANT_READ) { + /* GetInputData() hides a read want behind WS_FATAL_ERROR. Write + * wants come back by value, and a stale WS_WANT_WRITE from an + * accepted short send can outlive a terminal result. */ + ret = WS_WANT_READ; } return ret;