sshd_ossh_cert_test.sh generates its OpenSSH certificates into its own
work directory, through the OSSH_CERT_DIR that renew-ossh-certs.sh now
reads. Only the keypairs stay shared, and those are committed.
- the names carry just the login user, so two runs in one checkout
reissued each other's certificates and the force-command case failed
on a marker written to the other run's directory
The exit teardown stops the shared daemon before sweeping the registry.
stop_wolfsshd is what removes the per-daemon temp directory, which
holds the rewritten config and root-owned copies of the trust anchors,
and it is idempotent, so a run that stopped already is unaffected.
- a run that exits early, on a daemon that will not start or on a
failed test, reaches no stop of its own and left the directory in
/tmp with a copy of the host key in it
Dropping a stopped daemon from the run registry now installs the
rewritten file only when grep either kept lines or matched none. Any
other status is grep failing, and the empty file it leaves behind would
become the registry, losing every other daemon's pid.
- that registry is what the end-of-run sweep works from, so blanking it
strands whatever else the run started
stop_wolfsshd escalates to SIGKILL when SIGTERM has left the daemon
running after five seconds, and drops the pid from the run registry
only once it is gone. wolfsshd_alive answers whether the pid is still a
wolfsshd, which is what each of those steps turns on.
- the entry was dropped unconditionally, so a stuck daemon lost its
last handle and held its port for the rest of the run
- kill -0 says only that some process holds the pid, and this sends
SIGKILL as root, so a recycled pid was killed outright
- a recycled pid now counts as stopped and leaves the registry
start_wolfsshd now records whether the daemon's listening line appeared
in the ten seconds it waits for it. Without one it kills the daemon and
clears PID, which puts the start through the empty-PID check every
caller already has, so the run stops and names the daemon.
- the pid reaches the run registry before that check, so a daemon that
came up but never bound is still reaped by the runner's teardown
sshd_port_lease_test.sh forks processes that contend for real leases
and checks that a block is never held by two at once. It is the first
entry in test_cases, so a broken allocator is named there rather than
surfacing as a bind collision in an unrelated test much later.
- covers a contended stale block, parallel allocation, a live lease, a
live owner this run cannot signal, release and reuse, and --port
- a winner holds its lease until the parent releases it, so a straggler
cannot take a block already counted and read as a second winner
- the cross-user case drops to $SUDO_USER, the one arrangement that
tells "owner gone" from "owner not mine"
- contends over 29000-29511, below the ephemeral range and away from
the suite's daemon, with the scan pinned so --port has a block to skip
A run's port block is leased through a directory named for the block
and for the pid holding it, in a host-wide pool under /tmp. A run
creates and removes only its own lease, and ps decides whether an
existing one is still held. The allocator moves to port_lease.sh.
- a pool under $TMPDIR was not host wide: TMPDIR is per user on macOS
and sudo's env_reset drops it, so two runs kept private pools for one
set of ports
- a stale lease cannot be reclaimed in place: whatever does the
removing is authorized by an earlier read of the owner, so a second
runner displaces the live claim the first just made
- ps -p rather than kill -0, which reports failure both for a pid that
is gone and for one the caller may not signal -- opposite answers
when a non-root run reads a lease held by a live root run, as in CI
- the range starts at 28000, clear of the 22000-27999 that
scripts/fwd-bulk.test picks from and fails outright when taken
- a block holding the port passed to --port is skipped, so the shared
daemon and a private one cannot be assigned the same port
start_wolfsshd returns once the daemon accepts connections, not
once it has written its pid: wolfSSHd saves the PID file just
before tcp_listen(), so a caller connecting straight away could be
refused while the daemon's log showed no connection at all. A run
also claims its port block rather than only probing it.
- wait for the daemon's own "Listening on port" line, matched on its pid
so a previous daemon's line in the appended log cannot satisfy it
- take a block by creating its lock directory, which mkdir makes atomic,
and release it in the teardown; probing alone let two runners pick the
same block, and a claim whose owner is gone is treated as stale
- check that a pid from the PID file is still a wolfsshd, since kill -0
answers only whether some process owns the number
- fail the OpenSSH cert test when mktemp gives it no work dir: an
empty one reduced its teardown pattern to every wolfsshd present
The registry now holds only daemons that are still running, and it is
cleaned up however the run ends. Both private-daemon ports come from the
runner, so they stay inside the block it probed even when --port moves
the shared daemon off it.
- drop a pid from the registry once stop_wolfsshd has stopped it, so the
end-of-run sweep cannot reach a pid since recycled by another run
- run the sweep and the registry cleanup from an EXIT trap: every early
exit used to skip them and leave the file in /tmp
- export WOLFSSHD_PRIVDROP_PORT rather than re-deriving the offset in
sshd_privdrop_fail_test.sh, and correct that script's usage message
- quote the arguments to create_sshd_config.sh: an empty USER shifted
the port into $1, silently leaving the daemon on 22222
- name the three tests that still read the whole process table, which
are what keeps two concurrent runs from being fully independent
sshd_term_size_test.sh drives the client through a tmux session named
for the port it was given. The name used to be the constant "test",
which is shared across everything the user runs, so two concurrent runs
fought over one session and each EXIT trap killed the other's.
The OpenSSH certificate test kills its daemon with a pkill pattern. That
pattern now carries $WORK, the mktemp directory this invocation created,
so it names one run's daemon. "sshd_config_ossh" appears on every
concurrent run's command line, so the old pattern took their daemons
down too.
Two runs of the suite on one machine no longer collide. Each run takes a
block of ports, identifies the daemons it starts by the PID file in
their generated config, and at exit stops only those. wolfSSHd writes
that file after it has finished daemonizing, so the pid no longer has to
be guessed from what appeared in the process table.
- take a free block of ports per run, replacing the fixed 22222 and the
constants the private daemons used
- honour --port for a local run, and pass the port to
create_sshd_config.sh rather than baking it into the four configs
- read the daemon pid from a PidFile placed at the top of the generated
config, ahead of any Match block, where it is applied
- stop only the daemons recorded during this run, not every wolfsshd on
the machine
The sftp, scp and get-put scripts wait for the echoserver to publish its
port before connecting. Two seconds is not enough for a libtool re-exec,
the dynamic linker and the sample-key parse with a dozen sibling test
jobs on the machine, so a parallel make check failed them at their first
scenario. Ten seconds matches what sshclient.test already allows.
- raise the three wait loops from 20 to 100 iterations of 0.1 seconds
- test -s, not -e, after the loop in scp.test and get-put.test: the
ready file is created empty and the port written afterward, so -e can
take a file caught mid-write and yield an empty port
The helper drains a WS_EXTDATA and retries the read rather than handing
it back, so it is no longer error-code transparent for that one status.
Say so where the claim is made, and condense the rest.
A send the socket refused leaves WS_WANT_WRITE, and the retry branch
continues past the only tcp_select() in the iteration, which watches
reads. Wait on write readiness there, so a peer that has stopped
reading costs a descriptor wait rather than a spin.
- a buffered send with a willing socket still goes straight around
- an error-ready or failed descriptor ends the loop
- an interrupted select() retries instead of ending the session
wolfSSH_ChannelGetSessionGranted() reports whether a shell, exec or
subsystem request on a channel has been answered CHANNEL_SUCCESS, so an
application can tell a second request from the first without reaching
into WOLFSSH_CHANNEL.
- the flag is still clear for the request a session callback is
answering, so a set flag is an earlier request's grant
- wolfsshd's session callback reads the grant through it
wolfSSH_ChannelCommandIsScp() reports whether a channel's session
command starts an SCP transfer, taking "scp" only as its own token. The
divert in wolfSSH_accept() and an application's exec callback both ask
it, so the two cannot disagree about what the SCP server is handed.
- an exec of "scpbackup foo" runs as an ordinary exec
- a command carrying a NUL within the recorded command size is not an
SCP command; ParseScpCommand() walks a C string, so a NUL would drop
whatever follows it
- wolfsshd's session callback asks through the same helper
A shell, exec or subsystem request is answered as it arrives, through
the channel request callbacks, so a session this build cannot serve, or
a second one on a channel already running one, is refused with
CHANNEL_FAILURE rather than accepted and then dropped once the session
is up. What the daemon serves is unchanged.
- SessionRequestCb() takes a shell with WOLFSSH_SHELL, an exec with
WOLFSSH_SHELL or an scp command with WOLFSSH_SCP, and the sftp
subsystem with WOLFSSH_SFTP; anything else is refused and logged
- a request whose command did not fit is refused rather than read
through a NULL
- a second program start is refused on a channel whose grant already
stands, so sftp or scp cannot take over a running session
- the sftp name is matched whole and scp only as its own token, both
by length and bytes, so "scpbackup" or a name with an embedded NUL
is some other command
- sshd_bad_subsystem_test.sh asks for an unknown subsystem with the
OpenSSH client and expects the refusal
DoChannelRequestSession() serves the shell, exec and subsystem arms
together, and its debug line labels the string it read. A subsystem
request labels that string "subsystem" rather than "command".
- unit.c: renumber the shell-after-exec failure to follow the codes
above it
The generic channel request callback may free the channel it was handed,
so DoChannelRequest() looks it up again before the type handling reads
it. Gone, the request ends there, and a reply the peer wanted fails on
the missing channel the way one after a typed session callback does.
- ssh.h says the callback may free its channel and what the request does
from there
- regress.c frees the channel from the callback on each of the three
answers, and on a pty-req, the type that wrote to the channel outside
a session request
wolfSSH_CTX_SetChannelReqAnyCb() and wolfSSH_CTX_SetGlobalReqAnyCb()
register a callback consulted first for a channel or global request,
with the name, the type-specific part, and whether a reply is wanted. A
tri-state answer grants, refuses, or leaves it to the handling already
there, so a policy reaches the types with no hook of their own.
- the name is the one that arrived, since a copy into a buffer truncates
a long name and ends it at an embedded NUL, and a policy has to answer
on what the peer sent
- a grant still parses and records what the library needs, so a granted
session request commits the session, the typed callbacks are not
consulted, and a granted unknown type is answered CHANNEL_SUCCESS
- a port-0 tcpip-forward skips the callback, since only the forward
callback can report the port bound and a policy that had bound a
listener would then have to be refused, per RFC 4254 7.1
- a client refuses tcpip-forward and cancel-tcpip-forward ahead of any
policy, matched on the name that arrived so it holds in a build with
no forwarding, where neither name is in the name table
- window-change, exit-status and exit-signal are cleared of a reply
before the handling runs, so a refusal leaves them unanswered too, per
RFC 4254 6.7 and 6.10
- regress.c covers the answers, the data delivered, the names that do
not fit a copy, and which callbacks each answer leaves out
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
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
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 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() 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 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
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 merge step takes --failure-mode=all, so a raw profile left short by
a killed process is warned about and skipped instead of aborting the
report. Tests SIGKILL their servers in cleanup traps, and a process
killed while writing its profile leaves a corrupt header behind. The
step still fails when no profile can be read at all.
A shell, exec or subsystem request changes the channel only once the
callback accepts it. The session type and command are set for the
callback to read and put back if it refuses, and CLIENT_DONE follows
acceptance alone, so wolfSSH_accept() stays where it is rather than
reporting a session it answered CHANNEL_FAILURE as established.
- DoChannelRequestSession() carries the three arms, which differed only
in the type and the callback consulted
- a refusal puts the type and command back, so a grant an earlier
request won still stands
- FreeChannelCommand() wipes and releases a command line for both
ChannelDelete() and the refusal path
- unit.c drives a refused shell, exec and subsystem request through
DoChannelRequest() and checks nothing was committed, and that a
refusal after a grant puts the earlier command back whole
- regress.c checks accept() stays at ACCEPT_SERVER_CHANNEL_ACCEPT_SENT
on a refused shell, and that the sftp gate and both diverts ask for
the grant alone
Issue: F-8852
- wolfSSH_OutputPending() moves from wolfssh/internal.h to
wolfssh/ssh.h as a WOLFSSH_API taking a const WOLFSSH*, defined
in src/ssh.c beside the other public calls.
- wolfSSH_worker() puts the send's code in the return in place of
an event when the flush fails outright, and gates its flush on
wolfSSH_OutputPending(). wolfssh/ssh.h states both.
- Both wolfsshd shell loops, both echoservers and
ReceiveScpMessage() dispatch on wolfSSH_worker()'s return, and
call wolfSSH_get_error() only to tell a transient failure from a
terminal one. The POSIX wolfsshd loop sets wantWrite from
wolfSSH_OutputPending().
- Five worker tests expect the send's code where they expected the
event, and tests/testsuite.c calls wolfSSH_OutputPending().
- ScpStreamRead() calls _DumpExtendedData() and reads again when
wolfSSH_stream_read() returns WS_EXTDATA, reporting the drain's
own failure when it has one.
- ReceiveScpConfirmation() drops its WS_EXTDATA arm and reports a
negative read directly.
- ReceiveScpMessage() returns _DumpExtendedData()'s failure in place
of discarding it.
ReceiveScpConfirmation() takes a WS_EXTDATA off ScpStreamRead()'s
return, the rule ssh.h now states for reading an event.
FlushQueuedSend() samples the owed write with the session still
locked, since the peer reader thread writes ssh->error too, and
reports a send that failed there over the event it rode in on.
- A pass that delivers stderr while its flush short-writes returns
WS_EXTDATA with WS_WANT_WRITE in ssh->error, so the confirmation
read aborted the transfer with the stderr undrained.
- wolfSSH_stream_read() on the reader thread clears ssh->error with
this thread's packet still queued, which ended the flush early and
reported it a success.
- An event return with a hard send error latched collapsed to
WS_SUCCESS, telling the caller a queued packet reached a socket
that was gone.
- tcp_select_write() joins tcp_select(), with WS_SELECT_SEND_READY
at the end of the enum. tcp_select() passes NULL for writefds and
cannot wait on the write side.
- The Windows wolfsshd window-change drain and sftp_worker()'s
handshake flush retry wait on it. The drain records a give-up in
ret, and sftp_worker() breaks only on WS_SELECT_ERROR_READY.
- The echoserver, Espressif and both wolfsshd shell loops, and
ReceiveScpMessage(), take the event from wolfSSH_worker()'s
return when it carries one, in place of reading ssh->error alone.
The two echoservers cover WS_CHAN_RXD, WS_REKEYING,
WS_CHANNEL_CLOSED and WS_EOF; the wolfsshd loops and
ReceiveScpMessage() cover the ones they have arms for.
- The POSIX wolfsshd loop takes an owed write into wantWrite before
the event overwrites rc, and its WS_WANT_WRITE arm runs the
channel drain below in place of skipping it.
- FlushQueuedSend() folds the receive's own statuses into
WS_SUCCESS inside its loop and keeps flushing while
wolfSSH_get_error() reports WS_WANT_WRITE within the deadline, in
place of looping on the worker's return. It reports
WS_WANT_WRITE when the deadline leaves the packet queued.
- wolfSSH_worker() calls wolfSSH_SendPacket() whenever
ssh->outputBuffer holds bytes and the session is not
disconnected. ssh->error keeps the receive's code when the
receive failed, and the close's when a WS_CHANNEL_CLOSED pass
hard-failed its flush; WS_REKEYING is withheld on a failed
flush. Drops the second DoReceive(), the WOLFSSH_TEST_BLOCK
fork and the separate WS_CHANNEL_CLOSED flush.
- BundlePacket() resets ssh->outputBuffer.length to
ssh->packetStartIdx when the framing fails. wolfSSH_shutdown()
reports WS_WANT_WRITE when its close-read leaves output queued,
and the send's own error in place of it when that send failed.
SendPacketFlush() records its code in ssh->error on every
transport failure path, and wolfSSH_TriggerKeyExchange() writes
it only when SendKexInit() fails.
- portfwd, client and scpclient accept WS_WANT_WRITE from
wolfSSH_shutdown(); in scpclient the close-message drain runs
on it.
- wolfssh/ssh.h drops WS_WINDOW_FULL from wolfSSH_worker() and says
to read the return and wolfSSH_get_error() as independent channels
on every pass.
- Twenty unit tests and the extended TestWorkerReportsDisconnect
cover what ret and ssh->error hold after a receive, send, buffer,
callback or framing failure.
test_wolfSSH_set_fd() compares the NULL return of wolfSSH_get_fd()
against the platform invalid-socket sentinel, the value wolfSSH_new()
initializes rfd/wfd to. The old check only asserted the result was not
WS_SUCCESS, which the previous WS_BAD_ARGUMENT return also satisfied.
wolfSSH_accept() hands a session to the built-in SCP or SFTP server
only when the request naming it was answered CHANNEL_SUCCESS. A
callback that refuses one sends CHANNEL_FAILURE, yet sessionType and
command are recorded ahead of that answer and stay set, so the diverts
read a refused session as a served one.
- gate both diverts on channel->sessionGranted, as
wolfSSH_SFTP_accept() already gates the app-channels path
- the SCP divert tests the channel list itself, having no command
lookup ahead of it to do that
- cover each divert with a refused request and a granted control
DoChannelRequest() returns early when the header parse fails, so the
ret == WS_SUCCESS test that followed it could never be false. The
channel lookup moves into the else, which is the only way the function
reaches it.
Issue: F-11657
The peer's exec or subsystem command line is wiped ahead of both frees
that release it, ChannelDelete() and the GetStringAlloc() that replaces
it on a repeat request, the way ChannelDelete() already wipes the
decrypted inputBuffer just above. A command line can carry a password
or a token among its arguments.
- ScrubChannelCommand() leaves the free to GetStringAlloc(), so a parse
that fails behind it holds no dangling pointer
- cover both wipes with the retain-on-free allocator, the replacement
through wolfSSH_TestDoChannelRequest()
- release the test's hand-built channel on a setup failure
Issue: F-8850
DoChannelRequest() records the session type and asks the exec and
subsystem callbacks whether to grant a session only when the command
string parsed. A failed parse is refused on ret alone, and
channel->command still holds an earlier request's value rather than the
one being answered.
- cover a command length header running past the end of the packet, on
exec and on subsystem
Issue: F-11674
An application vetting an exec or subsystem request in its channel
request callback is handed the command as a C string, which stops at an
embedded NUL. wolfSSH_ChannelGetSessionCommandSz() and
wolfSSH_GetSessionCommandSz() report the parsed wire length, so a
callback can match a name whole the way DoChannelRequest() does.
- both accessors report 0 for a NULL channel or session
- wolfSSH_GetSessionCommand() defers to the channel accessor
- the sftp divert in wolfSSH_accept() asks the accessor for the length
- correct the trace name in wolfSSH_ChannelGetSessionCommand()
- cover a callback seeing "sftp\0evil" through exec and subsystem
CheckSftpAcceptRefusesUngranted() drives the same refusal two ways, a
registered subsystem callback saying no and app channels standing in
for a missing one, and asserted nothing that told them apart. Assert
the call count each case expects.
The built-in SFTP server takes a session only when the subsystem name
is sftp, matched whole. DoChannelRequest() keeps the parsed length in
channel->commandSz, so neither wolfSSH_SFTP_accept()'s grant gate nor
wolfSSH_accept()'s divert serves "sftpx" or "sftp\0evil".
- cover a granted name longer than sftp, one of its length, and one
running past an embedded NUL
- cover the divert with those three names and a control that diverts
- exec keeps its command length too
Issue: F-11665
wolfSSH_SFTP_accept() serves an application-driven session only on a
channel whose subsystem request was answered CHANNEL_SUCCESS.
DoChannelRequest() records the session type and command before it
decides, and leaves both set on a refusal, so they cannot say by
themselves whether anything was granted.
- add channel->sessionGranted, set from the answer a shell, exec or
subsystem request gets rather than from the request arriving
- look the channel up again before recording it: a callback may close
its own channel, and wolfSSH_ChannelFree() frees it
- log a request's strings where they are known good: once the parse
has succeeded, and ahead of a callback that may free the channel
- gate the app-channels path on that flag alongside the session type
and the command
- cover a refusal from both sides, no callback registered and a
callback that rejects, and a callback that frees its channel
In application-driven mode wolfSSH_accept() parks at userauth, so the
sftp test its divert applies never runs. wolfSSH_SFTP_accept() applies
it itself: the session channel must be a subsystem the application's
callback granted sftp on, or the call returns WS_INVALID_STATE_E and
leaves the wire alone without recording an error.
- gate the app-channels branch on wolfSSH_GetSessionType() and
wolfSSH_GetSessionCommand(), the same test accept() makes
- ask for that grant in every accept state: below the user-auth stop
accept() returns with no channel open, and past the stop there is no
accept() left that could have checked anything
- say in ssh.h that the mode serves SFTP through that grant and never
reaches the SCP entry point
- regress.c refuses the call with no channel, ahead of accept(), on a
granted shell and on an established one, and serves an INIT on a
granted sftp subsystem
DoChannelRequest() reads ssh->appChannels when the request arrives, so
turning the mode on after accept() established the session still refuses
an uncallbacked shell, exec or subsystem request from then on. Only
accept()'s stopping point is pinned, by the guard around stopState.
- say the flag reaches the requests that follow, and that what it cannot
do is move where accept() returns
- drive a shell request over the wire in both modes from the late-enable
test, pinning the behaviour the header now describes
wolfSSH_SetAppChannels() changes where wolfSSH_accept() stops and what
becomes of a session request with no callback behind it, so both modes
are exercised.
- regress.c drives a server with the pivot on, one with a shell
callback and one without, and checks accept() stops at
ACCEPT_SERVER_USERAUTH_SENT
- regress.c pins the context setter, the session's inheritance of it,
and that turning it on after accept() established the session still
returns
- regress.c re-enters a parked accept() with output still queued, which
is the one path that flushes before reading the state, and pins that
it leaves the state on the stop
- unit.c checks DoChannelRequest() refuses a shell, exec and subsystem
request with no callback once the pivot is on
A server that wants to own its channels had no way to get them: accept()
ran the session state machine to the end, and a shell, exec or subsystem
request with no callback registered was granted regardless.
- add wolfSSH_CTX_SetAppChannels() and wolfSSH_SetAppChannels(), off by
default, a byte on the context copied into the session
- on, accept() returns once the user is authenticated, and a session
request with no callback behind it is refused: nothing is left to serve
- keep the stop state out of the pending-send advance, so a re-entry with
queued output cannot step over where this call is meant to stop
- stop early only while the session is short of that state, so turning the
mode on afterward cannot leave the loop hunting a state it went past
- teach wolfSSH_SFTP_accept() that the mode parks accept() short of an
established session, so it stops redoing the handshake on every poll