Skip to content

echoserver: answer session requests in callbacks - #1234

Open
ejohnstown wants to merge 7 commits into
wolfSSL:masterfrom
ejohnstown:ccb-phase2-4e
Open

ejohnstown wants to merge 7 commits into
wolfSSL:masterfrom
ejohnstown:ccb-phase2-4e

Conversation

@ejohnstown

@ejohnstown ejohnstown commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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.

  • wsShellStartCb() forks the pty, so it is registered in both modes and claims the channel only once there is a shell behind it.
  • wsExecStartCb() takes an "scp " command as a transfer and any other command as an echo session; wsSubsysStartCb() guards a NULL command.
  • ssh_worker() drives the session through shellCtx.appFd, and claims the channel itself when no callback did.
  • Resume a subsystem accept that returns a want, waiting on the socket between attempts rather than spinning.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 adjust wolfSSH_accept()/channel-request handling accordingly.
  • Expose server-side helpers for app-driven flows (notably wolfSSH_SCP_accept() and wolfSSH_AGENT_ChannelOpen()), and adapt SFTP accept behavior in this mode.
  • Update echoserver to support a -A mode (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.

Comment thread wolfssh/agent.h Outdated
Comment thread wolfssh/ssh.h Outdated

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread examples/echoserver/echoserver.c Outdated
Comment thread examples/echoserver/echoserver.c
Comment thread examples/echoserver/echoserver.c Outdated
Comment thread src/agent.c
@ejohnstown ejohnstown self-assigned this Sep 3, 2026
@ejohnstown
ejohnstown force-pushed the ccb-phase2-4e branch 2 times, most recently from 987a00d to 60063d2 Compare September 9, 2026 22:52
Comment thread examples/echoserver/echoserver.c Outdated
Comment thread examples/echoserver/echoserver.c
Comment thread examples/echoserver/echoserver.c Outdated
Comment thread src/agent.c
@ejohnstown
ejohnstown marked this pull request as ready for review September 11, 2026 00:26

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread examples/echoserver/echoserver.c
Comment thread examples/echoserver/echoserver.c

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread examples/echoserver/echoserver.c
Comment thread examples/echoserver/echoserver.c Outdated

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread examples/echoserver/echoserver.c
Comment thread examples/echoserver/echoserver.c Outdated
Comment thread examples/echoserver/echoserver.c Outdated
@ejohnstown
ejohnstown force-pushed the ccb-phase2-4e branch 3 times, most recently from fc6ea3c to 46a006b Compare September 11, 2026 21:40
@wolfSSL-Fenrir-bot
wolfSSL-Fenrir-bot dismissed their stale review September 11, 2026 22:40

Fenrir's latest completed scan found no issues; clearing the prior automated change request.

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@wolfSSL-Fenrir-bot
wolfSSL-Fenrir-bot dismissed stale reviews from themself September 11, 2026 22:40

Fenrir's latest completed scan found no issues; clearing the prior automated change request.

@wolfSSL-Fenrir-bot
wolfSSL-Fenrir-bot dismissed their stale review September 11, 2026 22:40

Fenrir's latest completed scan found no issues; clearing the prior automated change request.

@ejohnstown ejohnstown assigned wolfSSL-Bot and unassigned ejohnstown Sep 11, 2026
padelsbach
padelsbach previously approved these changes Sep 14, 2026
Comment thread examples/echoserver/echoserver.c
@padelsbach

Copy link
Copy Markdown
Contributor

@ejohnstown, can you resolve conflicts pls?

padelsbach
padelsbach previously approved these changes Sep 14, 2026

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread examples/echoserver/echoserver.c Outdated
Comment thread examples/echoserver/echoserver.c
Comment thread examples/echoserver/echoserver.c
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

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread examples/echoserver/echoserver.c
@ejohnstown

Copy link
Copy Markdown
Contributor Author

Two items are deliberately deferred out of this pull request, recorded here so neither is lost:

  • App-driven SFTP under forced write blocking deadlocks. The SFTP contention test jobs fail on this branch and only on this branch, because this branch adds the only sftp.test case that starts an -A echoserver. Reproduced locally at WOLFSSH_BLOCK_PROB=70: both ends park in a read wait, the server inside wolfSSH_accept() at DoReceive -> GetInputData -> wsEmbedRecv -> select, with output owed and unflushed. It never reaches sftp_worker(), so PR echoserver: wait to write in the SFTP loop #1250's write wait there does not bear on it -- cherry-picked and tested, still hangs.
  • A granted, unsupported subsystem is left unserved in legacy mode. Analysis is in the resolved review thread on examples/echoserver/echoserver.c. The fix registers a subsystem callback in legacy mode as well, which changes long-standing echoserver behaviour and wants its own commit.

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants