echoserver: answer session requests in callbacks - #1234
ejohnstown wants to merge 7 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Public-header documentation for new APIs/modes is currently inconsistent with the implemented return semantics and supported flows, which can mislead API consumers.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR extends wolfSSH’s server-side channel handling to support an opt-in, application-driven channel mode where wolfSSH_accept() stops after user authentication and the application drives session/channel progress via callbacks and wolfSSH_worker(). It also updates the echoserver example and adds/extends tests to cover the new behavior and retry semantics for non-blocking flows.
Changes:
- Add application-driven channel mode (
wolfSSH_CTX_SetAppChannels(),wolfSSH_SetAppChannels()) and adjustwolfSSH_accept()/channel-request handling accordingly. - Expose server-side helpers for app-driven flows (notably
wolfSSH_SCP_accept()andwolfSSH_AGENT_ChannelOpen()), and adapt SFTP accept behavior in this mode. - Update echoserver to support a
-Amode (callbacks start shell/SFTP/SCP sessions) and add regression/unit test coverage.
File summaries
| File | Description |
|---|---|
| wolfssh/wolfscp.h | Documents new server-side SCP accept API for application-driven transfers. |
| wolfssh/ssh.h | Documents new application-driven channel mode and adds public setters. |
| wolfssh/internal.h | Adds appChannels flag to WOLFSSH_CTX and WOLFSSH structs. |
| wolfssh/agent.h | Declares wolfSSH_AGENT_ChannelOpen() for app-driven agent forwarding channel open. |
| src/ssh.c | Updates wolfSSH_accept() stop-state logic; adds setters for app-channels; refactors agent open to new helper. |
| src/internal.c | Changes default “no callback” behavior for shell/exec/subsystem requests when app-driven mode is enabled. |
| src/wolfsftp.c | Makes wolfSSH_SFTP_accept() treat app-driven accept stop-state as “accept done” for callers. |
| src/wolfscp.c | Implements wolfSSH_SCP_accept() wrapper over SCP request handling with WANT propagation. |
| src/agent.c | Implements wolfSSH_AGENT_ChannelOpen() helper for server-side agent channel open. |
| tests/unit.c | Adds unit coverage ensuring no-callback session requests are refused in app-driven mode. |
| tests/regress.c | Adds regression coverage for accept stopping at userauth, callback dispatch, inheritance, and late-enable behavior. |
| tests/api.c | Adjusts keyboard-interactive test client args to avoid shell-callback refusal in echo mode. |
| examples/echoserver/echoserver.c | Adds -A mode and callback-driven session start; updates non-blocking retry loops for SFTP/SCP; refactors shell startup into callback. |
Review details
- Files reviewed: 13/13 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1234
Scan targets checked: wolfssh-bugs, wolfssh-src
Findings: 4
4 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
987a00d to
60063d2
Compare
60063d2 to
0810b12
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1234
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1234
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 2
2 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
7eb0a6d to
2d64daa
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1234
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 3
3 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
fc6ea3c to
46a006b
Compare
Fenrir's latest completed scan found no issues; clearing the prior automated change request.
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1234
Scan targets checked: wolfssh-src, wolfssh-bugs
Fenrir result: Approved ✅
No new issues found in the changed files.
Advisory only — this automated result does not count as a GitHub approval.
Fenrir's latest completed scan found no issues; clearing the prior automated change request.
Fenrir's latest completed scan found no issues; clearing the prior automated change request.
|
@ejohnstown, can you resolve conflicts pls? |
6caa978 to
eef5ff2
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1234
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 3
3 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
With -A the echoserver drives its own channels: accept() returns at userauth and the callbacks below start the shell, SFTP or SCP session. Off by default. The two modes are exclusive, since the callbacks answer the session requests the accept state machine otherwise answers itself. - wsShellStartCb() forks the pty, so it is registered in either mode, and claims the channel only once there is a shell behind it; a second request is refused rather than forking over the running shell - wsExecStartCb() takes an "scp " command as a transfer and any other command as a session, and is registered in either mode, since the legacy path has always started a shell for an exec request too - wsSubsysStartCb() is registered only with -A, as accept() serves sftp itself, and guards a NULL command, which a truncated request leaves behind - ssh_worker() drives the session through shellCtx.appFd, claims the channel itself when no callback did, and leaves an SFTP or SCP handoff through its cleanup so the pty master still closes - open the agent channel from the select loop, since the peer's auth-agent-req lands after accept() has returned, and read the listener from the context each pass because it appears mid-loop - resume a subsystem accept that returns a want, waiting on the socket between attempts rather than spinning - close the accepted socket again, clear fwdFd on EOF or reset, and stay in the loop on WS_REKEYING, which the read arm already handles - key ChildRunning's sig_atomic_t on WOLFSSH_SHELL, the only build with the SIGCHLD handler that writes it, so a target whose libc has no signal.h still compiles - ask for echo mode in the keyboard-interactive test, which has no account on the host for the shell callback to fork a shell for
The echoserver's -A mode runs an accepted scp command through wolfSSH_SCP_accept(). Reaching that call's want retry path takes a non-blocking server, which -N supplies. - copy to and from an app-driven server in scp.test - check the entry point's null-session argument
ssh_worker() takes the session channel accept() established only when the request was granted and there is somewhere to put the data. The type and command stay set on a refusal, so they do not say what was granted, and a refused request leaves nothing running. - a shell build serves the channel through the pty, so with no shell started only echo mode can take it - wsShellStartCb() closes the pty and reaps the child when the terminal setup fails, installs ChildSig() only once the session will run, and leaves ChildRunning alone when forkpty() fails - the connection holds the shell's pid, so the worker loop's exit and an accept() whose reply failed end the child too - the shell, exec and subsystem callbacks share SessionInUse(), so a second program start on the connection is refused - the EOF drain answers a half-close only on a claimed session, rather than on whatever channel id 0 finds
The subsystem and exec callbacks pick out the same commands the accept state machine does: sftp matched whole, by length and bytes, and scp on the three-byte prefix ChannelCommandIsScp() takes. - sftp with an embedded NUL is refused, rather than granted and then dropped by wolfSSH_SFTP_accept() - a transfer is granted only on the head channel the accept APIs serve, so a request on a later channel is refused rather than answered success
ssh_worker() reads a WS_WANT_WRITE from wolfSSH_worker() as a send the socket has not taken yet: it waits for the socket to accept one and runs the worker again, rather than leaving the loop. Application-driven mode answers session requests here, and the peer waits on that reply, so a blocked one ended a session the legacy accept() carried through. - the worker runs on a writable socket as well as a readable one, or the owed send is never retried - the ssh socket joins the select write set only while a want-write is outstanding - a queued agent channel open joins the same wait, so the poll runs ahead of the write set
The -A cases in scp.test reach the exec callback and the scp handoff. These drive the rest: the subsystem callback with the sftp accept, the shell callback in echo mode, and both accepts on a blocking server. - sftp.test connects to an -A server blocking and non-blocking - sshclient.test runs a terminal session and a command session against an -A server - scp.test copies from a blocking -A server, where wolfSSH_SCP_accept() completes in one call rather than through the retry loop
eef5ff2 to
3e6ff9c
Compare
wolfSSL-Fenrir-bot
left a comment
There was a problem hiding this comment.
Fenrir Automated Review — PR #1234
Scan targets checked: wolfssh-src, wolfssh-bugs
Findings: 1
1 finding(s) posted as inline comments (see file-level comments below)
This review was generated automatically by Fenrir. Reported findings require changes before merge.
|
Two items are deliberately deferred out of this pull request, recorded here so neither is lost:
|
The non-blocking app-driven case runs in every other build. Under forced blocking the two ends deadlock -- the server sits in DoReceive with output it owes still queued, the client waits for that output -- which is a defect of its own to fix rather than this case failing. - blockBuild names the macro being set, which nonblockingOnly already stood for on a different question
With -A the echoserver drives its own channels: accept() stops at userauth and the callbacks below start the shell, SFTP or SCP session. Off by default, and the two are exclusive.