Commit Graph

3059 Commits (399c83b1b6ea17f58ebc0638b82da9e6a6c222be)

Author SHA1 Message Date
JacobBarthelmeh 0be0fae009 use wolfSSH_ prefix on wolfSSH specific config's HostKeyStore, HostKeyStoreSubject, HostKeyStoreFlags 2026-09-05 01:32:02 -07:00
JacobBarthelmeh ed9bf644d0 use proper free for cert store version, fix echoserver on mac os 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 4baba585d5 fix for flags handling, ocsp case, macro guards, unused variable, changed default from MY to required to be set
enable SHA1 with windows cert store test case

expand test cases, adjust to authorized key file, minor dead code adjustments

add more documentation, refactor duplicate code sections, clean up test cases, more adjustments to logging spamming protections
2026-09-05 01:32:02 -07:00
JacobBarthelmeh 13c2adf34b add Windows cert store use with signing and add example arguments
add Windows cert store test case

make windows cert feature default disabled and simplify macro guard

additional unit tests, advertise x509 and pubkey, use CN to match username, build check for WOLFSSL_SYS_CA_CERTS, fix for CM ref count

additional build test, uniform enum name, fail on unkown cert store ecc curve, tie in of loading whole cert store for sys CA's
2026-09-05 01:32:02 -07:00
Kareem 2584a027b4 Prefix wolfSSH specific options with wolfSSH_. 2026-09-05 01:32:02 -07:00
Kareem dd955673a4 Add support for loading user CA certs from a configurable Windows cert store. 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 440373681a add macro guard for system ca certs load 2026-09-05 01:32:02 -07:00
JacobBarthelmeh 37e75d4aa0 add TrustedSystemCAKeys sshd option for system CA load 2026-09-05 01:32:02 -07:00
John Safranek fb15a05ed0 tests: cover the forward cleanup callback
WOLFSSH_FWD_LOCAL_CLEANUP now fires, and what must not fire matters as
much as what must: the gate is the channel's own setup record, not its
type.

- a peer-opened direct-tcpip channel reports SETUP, CHANNEL_ID, then
  CLEANUP when it closes
- a forwarding channel this side opened reports no cleanup, and a
  session channel no forwarding action at all
- an open that fails after a successful setup still reports the cleanup,
  and a setup that reported failure draws none
- freeing the session, or the channel with wolfSSH_ChannelFree(),
  reports it, and the session free after a channel free does not
  report it again
2026-09-03 21:43:30 -05:00
John Safranek 6f9b021f0d portfwd: stop double-closing the forwarded socket
The LOCAL_CLEANUP handler closes the target socket, and portfwd_worker()
closed its own copy of the same descriptor again at teardown. That was
unreachable while the library never emitted the action, which is why the
report against it was set aside; it is reachable now.

- record the cleanup in the forwarding state and skip the second close
- clear the shared descriptor when the exit path is the one that closes
  it: the loop can leave with the channel still open, and freeing the
  session then runs the handler on a descriptor already closed
- reset the record when a fresh forward's socket is adopted, or one left
  by an earlier refused open skips closing a live socket at exit
- drop the handler comment saying the action is never emitted
2026-09-03 21:43:30 -05:00
John Safranek 70b22bd97d echoserver: let the cleanup callback tear down
WOLFSSH_FWD_LOCAL_CLEANUP now runs, and it runs from DoChannelClose()
ahead of the WS_CHANNEL_CLOSED the worker sees. The handler has already
closed the socket and moved the state on by then, so the recovery branch
no longer matched and left ssh_worker() holding a closed descriptor.

- guard the handler's close: the open can fail after the setup, with
  nothing yet connected
- gate the handler on the channel id the library passes in the port
  parameter. A channel can outlive its turn in the single forwarding
  slot, and a cleanup arriving after the next forward has moved in
  would close that one's live socket
- have the recovery branch clear its stale copy of the descriptor when
  the handler got there first, and still do the whole teardown for a
  locally opened forward, which draws no callback
- resolve the closed channel with wolfSSH_GetLastRxId(). wolfSSH_worker()
  names the channel only for the data and EOF statuses, so the recovery
  branch was comparing against a stale zero and ran only for a forward
  that happened to be channel 0
- clear the pending direct connect as well: it is only cleared on
  success, so a refused target left it set and the worker connected
  again with the host name the handler had just freed
2026-09-03 21:43:30 -05:00
John Safranek 6485062d3d internal: send LOCAL_CLEANUP on forward delete
WOLFSSH_FWD_LOCAL_CLEANUP is part of the public WS_FwdCbAction contract
and the library never sent it, so an application's handler never ran and
every peer-opened forward leaked what its setup callback allocated.
ChannelDelete() now sends it, so a peer close, an open refused after the
setup ran, wolfSSH_ChannelFree(), and freeing the session all report it.

- record the setup in a new fwdSetupTxd bit on WOLFSSH_CHANNEL: a
  locally opened forward gets no LOCAL_SETUP, so the channel type alone
  cannot say whether the application holds anything
- clear the bit when the cleanup goes out, so a channel reports it once
- pass the channel's id in the port parameter, the way
  WOLFSSH_FWD_CHANNEL_ID does, so an application with two forwards can
  tell which one ended
- TestDirectTcpipFwdCbRejectsChannelId now counts three callback calls
2026-09-03 21:43:30 -05:00
John Safranek f8b72ac079 tests: move bulk data through a forward
scripts/fwd.test sends eight short lines, so nothing in the suite drives
a forwarding channel past its first window, and a forward that stalled
once the window needed crediting would go unnoticed.

- push a payload several windows long through a local direct-tcpip
  forward and compare the bytes that arrive
- end a second transfer one window plus a short tail in, where the tail
  is what portfwd still holds at end-of-input, and repeat it since the
  window credit beating that tail is a race
- give the listening nc its stdin from a fifo a sleep holds open:
  reading end-of-input makes nc close the connection, which truncates
  the transfer and looks exactly like a stall
- bail out early once the byte count stops moving, so a real stall
  reports in seconds
- dump the logs on failure, check the listening nc came up, and take
  ports clear of the ones fwd.test.expect hardcodes, so a squatted or
  shared port is not reported as a stall
2026-09-03 20:40:55 -05:00
John Safranek 31f831b7ad portfwd: drain the buffer before leaving on EOF
A zero read on the local socket left the loop at once, and anything
read but not yet accepted by wolfSSH_ChannelSend() went with it. With
the peer's window full that tail is up to a buffer's worth, so a
transfer that ends while the window is being credited comes out short.

- stop polling the socket on end-of-input and keep looping until the
  buffer is empty
- leave anyway once the channel is gone, so a forward whose open failed
  cannot hold the loop on a buffer nothing can take
2026-09-03 20:40:55 -05:00
John Safranek ee7fa04bec ssh: flush any queued output at teardown
wolfSSH_shutdown() flushes whatever a short send left in the output
buffer, not just a queued disconnect. A rejected auth's
USERAUTH_FAILURE has no channel, and DoChannelClose() retires the
channel as soon as the close is bundled, so neither had anything
left to carry the retry.

- The gate still refuses a flush once the peer has disconnected,
  unless our own disconnect is the thing queued.
- A flush that finishes on a live session clears the WS_WANT_WRITE
  the short send latched, as the disconnected path already did.
- ssh.h documents the widened flush and the WS_WANT_WRITE a short
  one leaves wolfSSH_shutdown() returning.
- Tests cover the rejected auth with no channel and the close whose
  channel DoChannelClose() already retired.
- A flush that short-sends on a live channel leaves the teardown
  queued behind it. The retry adds no second EOF or close, and skips
  the close wait while its own flush is still short.
- A teardown send that drained the leftovers settles the flush; one
  that failed outranks it, since a reset leaves the buffer intact.
2026-09-03 16:12:17 -05:00
John Safranek 54610fd36e tests: cover the exec and subsystem req callbacks
Only the shell hook had coverage, so nothing held DoChannelRequest() to
handing the command and session type to the exec and subsystem callbacks
or to answering with what they return.

- assert each callback sees the session type and the command string the
  request carried, and the ctx set on the session
- assert an accepting callback draws CHANNEL_SUCCESS and a rejecting one
  CHANNEL_FAILURE, whether it rejects with a WS_ error or a bare nonzero
2026-09-03 15:31:58 -05:00
John Safranek 3c6c1e85d8 tests: cover the channel close callback
wolfSSH_CTX_SetChannelCloseCb() had no caller in tests/, examples/ or
apps/, so nothing held DoChannelClose() to running the hook before it
retires the channel.

- assert the callback runs with the closing channel's id and the ctx
  set on the session
- assert the channel is still on the list inside the callback and gone
  by the time the caller is told
- a rejecting callback changes nothing: the return is discarded and the
  close completes
2026-09-03 15:31:58 -05:00
John Safranek 6804e999e1 tests: cover the channel open response callbacks
The confirmation and failure hooks reach an application only through
DoChannelOpenConf() and DoChannelOpenFail(), and nothing in tests/ or
apps/ registered either, so both arms shipped unexercised.

- assert the confirm callback runs with the peer's channel id, window
  and packet size already recorded
- assert the failure callback runs while the channel is still findable,
  before DoChannelOpenFail() removes it
- count each hook separately, so a test can tell which one ran
- a rejecting confirm hook fails the receive and leaves the open
  unfinished; a rejecting failure hook fails it and leaves the channel
  on the list
- seed the unconfirmed channel through ChannelNew() and ChannelAppend(),
  the state an outstanding open leaves behind
2026-09-03 15:31:58 -05:00
John Safranek 3d77ce5851 Stop the linker folding main() into a skipped test on macOS
When a test is configured out, its entry function compiles down to
"return 77", the automake skip code, and the main() that forwards to it
inlines to the same two instructions. Apple's ld folds the two identical
bodies together and then writes LC_MAIN entryoff 0, so the binary starts
executing at the Mach-O header and dies with SIGILL instead of skipping.
tests/kex.test, tests/api.test and tests/auth.test all hit this under
--enable-all CPPFLAGS=-DWOLFSSH_TEST_BLOCK, and kex.test hits it again
under --disable-server.

Probe for -Wl,-no_deduplicate and add it to AM_LDFLAGS when the linker
takes it. GNU ld rejects the flag, so the check leaves Linux alone.

Making the skipped body differ does not help: anything added to the entry
function inlines into main() and the two fold again.
2026-09-03 11:41:07 -05:00
John Safranek 32640229ec tests: pick the sshd child that arrived
sshd_sftp_idle_cpu_test.sh measures the connection process it forked, so
it takes the wolfsshd present after the connection and not before. The
old symmetric difference offered a pid that left during the window just
as readily, and the smallest one wins, so an earlier test's departing
child was measured through a /proc entry that no longer existed.

- compare the pid sets one way, and poll for the fork rather than
  sampling a fixed five seconds in
- let the handshake and SFTP setup finish before the baseline, so their
  ticks land outside the measurement rather than inside it
- print both pid sets when no child is found, since the failure says
  nothing about which pids were considered
2026-09-03 11:29:54 -05:00
John Safranek 79456c2002 Refuse a session channel open on a client
RFC 4254 6.1 has a session open travelling client-to-server, so
DoChannelOpen refuses one on a client endpoint in the type switch,
ahead of ChannelNew and any channelOpenCb. OpenSSH and Dropbear
refuse it outright too, with no application opt-in.

- Answer OPEN_ADMINISTRATIVELY_PROHIBITED, as the forwarded-tcpip
  direction gate below it does.
- Widen the client harness guard in regress.c so it builds without
  WOLFSSH_FWD, and hoist AcceptChannelOpenCb out of the forwarding
  block.
- Cover the refusal, and the gate outranking an accepting
  channelOpenCb.

Issue: F-8836
2026-09-03 10:40:16 -05:00
John Safranek b13704af1d Cover and document the client KEX role check
TestClientOnlyKexMsgsBlocked now asserts that 31 and 33, the ids a
client does receive, stay allowed where the handshake expects them, so
widening the role check into a 30-34 range fails the suite. The
IsMessageAllowed() comment records the receive-only policy and the
per-KEX-method id namespace.

- assert id 33 is allowed once expectMsgId is MSGID_KEXDH_GEX_REPLY
- assert expectMsgId starts at MSGID_NONE, so the role check is what
  rejects the blocked ids
- recheck all three blocked ids during a rekey on an established session
2026-09-03 10:32:18 -05:00
Aidan Keefe 557f3df42d Validate a custom SSH proto ID string
wolfSSH_CTX_SetSshProtoIdStr() now rejects a string that is not
CRLF-terminated, exceeds 255 bytes counting the terminator, or
carries a CR or LF in the body. DoKexInit() subtracts the two
terminator bytes from the length when hashing it, so an
unterminated string underflowed the hash length.

Issue: F-10571
2026-09-02 15:36:18 -07:00
Emma Stensland ea530fe05d wolfsftp: Improve POSIX and Windows write/append handling
Ensures EOF appending on all POSIX systems and fails short writes on Windows.
Consolidates the Windows SFTP flag matrix testing with shared helpers.
2026-09-02 15:35:09 -07:00
Emma Stensland 5a1ff71acd wolfsshd: Stop option parsing when a Windows argv conversion fails 2026-09-02 15:35:09 -07:00
Emma Stensland 6a71292401 internal: Refactor and gate ML-DSA composite key handling
Gates ML-DSA composites behind WOLFSSH_NO_MLDSA_COMPOSITES.
Deduplicates key handling and uses heap allocation for
composite buffers when compiling for small stacks.
2026-09-02 15:35:09 -07:00
Emma Stensland a513f69f1d internal: Share one bounded ECDSA r/s mpint encoder 2026-09-02 15:35:09 -07:00
Emma Stensland 50ad9e150a wolfsshd: Check authorized_keys types against the key blob 2026-09-02 15:35:09 -07:00
John Safranek 5e72c76683 Rebind a request in flight to the folded forward
FwdRemoteSettle() folds a port-0 forward the peer bound onto a
registration already standing at that port. A request still in its
send window resolved its forward before the send, so it names the
survivor from here on, the same as the queued replies do.

- Add FwdPendingRebind() and call it ahead of the unlink, so a cancel
  whose send pumped in the answer commits naming a forward rather
  than nothing.
- Regression test drives the port-0 answer in from the cancel's send
  callback.
2026-09-02 15:18:32 -05:00
John Safranek cb211506e2 Fold a forward the send settled onto its bind
FwdPendingCommit() looks for a registration of the bind its entry names
before linking it. An answer pumped in from the send can settle a
port-pending forward onto that port, and the fold in FwdRemoteSettle()
runs before the committing entry joins the list.

- replies queued against the folded entry move to the one that stands
  at the bind
- test drives that answer from the IO send callback; the bind takes two
  registrations without the fold, and one cancel leaves one matching
2026-09-02 15:18:32 -05:00
John Safranek e53c157049 Resolve a forward request once, before its send
A request looked its registration up again at commit, after the send had
run the application's send and highwater callbacks. A callback that
reenters the library sends behind the request that ran it, so the lookup
found what the callback did rather than what the request asked for: a
cancel from a first setup's send found nothing to take, and that setup's
commit then registered the forward the peer had just been told to drop.

- Requests in flight hang off ssh->fwdPendingHead for the length of
  their send, so a reentrant request resolves to the forward the request
  it interrupted is registering, not to nothing.
- FwdRemotePrepare() settles what the request names and holds it on the
  pending; FwdPendingCommit() uses that instead of a fresh lookup, which
  also drops the dedupe branch the double lookup needed.
- FwdRemoteUnlink() voids the pending pointers along with the queued
  slots, so freeing a forward mid-send leaves no commit naming it, and
  FwdPendingDiscard() goes through it rather than freeing raw.
- FwdRemoteMatch() reads the pending list under the rules the scan over
  registered forwards uses: a setup mid-send is on the wire and can feed
  channels; a cancel behind it or a refusal on its slot stops matching.
- Drop pend->bindAddr and pend->bindPort, which nothing reads now.
- Tests drive a cancel, a setup, an inbound forwarded-tcpip open and a
  mid-send REQUEST_FAILURE from the IO send callback, the window the
  highwater tests no longer reach.

Issue: ZD-22195
2026-09-02 15:18:32 -05:00
John Safranek 79a7f29933 Match forwarded-tcpip on every client session
The match only ran once wolfSSH_FwdRemoteSetup() had registered
something, so a client that never called it had every forwarded-tcpip
open go unchecked -- the case RFC 4254 7.2 is clearest about, since a
session that asked for no forward has nothing an open could answer for.

- Refuse the open whenever it names no registration, empty list
  included. wolfSSH_SetFwdRemoteMatch(WOLFSSH_FWD_MATCH_OFF) is the
  opt-out for an application that keeps its own bind list.
- Drop fwdRemoteTracked, which nothing reads now.
- wolfSSH_global_request() cannot carry the bind address and port RFC
  4254 7.1 puts after the want-reply boolean, so no request that
  registers a forward can be framed outside wolfSSH_FwdRemoteSetup().

Issue: ZD-22195
2026-09-02 15:18:32 -05:00
Reda Chouk 6802df0c52 Reject the KEX requests a client never receives
This is just a follow-up PR to (#1221), that mirrors pretty much the same changes for the client side too.
2026-09-02 11:20:06 -07:00
Hideki Miyazaki 4ee445dd07 Addressed comments 2026-09-02 09:17:30 -07:00
Hideki Miyazaki 88c0e34b7e Addressed review comments 2026-09-02 09:17:30 -07:00
Hideki Miyazaki 1c5fbfe0b5 Addressed review comments 2026-09-02 09:17:30 -07:00
Hideki Miyazaki 593216020e fix Windows SFTP 2026-09-02 09:17:30 -07:00
Yosuke Shimizu f1b6a99d56 wolfsftp, port: apply the attributes SETSTAT and FSETSTAT acknowledge
- SFTP_SetFileAttributes() and SFTP_SetFileAttributesHandle() carry
  out the size, ownership, permission and timestamp requests while ret
  is WS_SUCCESS, bound the size with wResolveOffset() against
  WOLFSSH_MAX_FILE_OFFSET, and set WS_UNIMPLEMENTED_E where the port
  defines no wrapper.
- wolfSSH_SFTP_RecvSetSTAT() and wolfSSH_SFTP_RecvFSetSTAT() answer
  WOLFSSH_FTP_UNSUPPORTED for WS_UNIMPLEMENTED_E; wolfSSH_SFTP_CHMOD()
  sets the attribute flags to WOLFSSH_FILEATRB_PERM before sending.
- port.h adds WTRUNCATE, WFTRUNCATE, WCHOWN and WFCHOWN for the POSIX
  port, and defines WSETTIME and WFSETTIME over the existing WUTIMES
  and WFUTIMES helpers in place of their (0) definitions.
- SFTP_SetMode() guards on _WIN32_WCE in place of USE_WINDOWS_API, and
  port.c adds WS_ChmodA(), which trims the SFTP leading root and calls
  _wchmod(); it and the WCHMOD mapping to it are left out under
  _WIN32_WCE, which keeps the _chmod mapping.
- tests/regress.c adds TestSftpSetStatAttributes(); tests/sftp.c adds
  a chmod of a directory and installs SFTP_TEST_UMASK in place of the
  sftpTestUmask static; the STATE_SET_ATR_SEND case in tests/api.c
  sets atr.flags to WOLFSSH_FILEATRB_PERM.

Issue: F-11658
2026-09-02 08:42:36 -07:00
Reda Chouk a472f1ee2b Reject the KEX replies a server never receives 2026-09-02 08:30:09 -07:00
Yosuke Shimizu 5151732d6b wolfsftp: keep the local file when resuming a get
- The Windows local open in wolfSSH_SFTP_Get() passes OPEN_EXISTING
  when the write offset is nonzero and CREATE_ALWAYS otherwise, and
  drops FILE_APPEND_DATA from the desired access. A new
  DWORD creationDisp replaces the block-scoped desiredAccess.
- That open reports INVALID_HANDLE_VALUE as WS_BAD_FILE_E and moves to
  STATE_GET_CLEANUP; the OVERLAPPED offset is set from gOfst on every
  open rather than only when resuming.
- STATE_GET_LOOKUP_OFFSET clears a saved offset when the remote size
  STATE_GET_LSTAT stored in state->attrib is no larger than it, and
  again when the local destination does not hold exactly that many
  bytes. The destination stat overwrites state->attrib.
- tests/api.c adds test_wolfSSH_SFTP_GetResume() and its
  sftpGetToCompletion() helper, six cases over the resume paths, built
  where the hosted file wrappers are available.
- test_wolfSSH_SFTP_PutResume() and test_wolfSSH_SFTP_GetResume() drop
  the WOLFSSH_ZEPHYR k_sleep() block their bodies exclude.
- .gitignore covers every wolfssh_*.tmp the api tests leave behind on
  an aborted run, replacing the known_hosts-only entry.

Issue: F-12547
2026-09-01 09:19:10 -07:00
John Safranek c71202ffdb Guard remote forwards and disallowed msg ids
A client answers tcpip-forward and cancel-tcpip-forward with a failure,
RFC 4254 section 7.1, before the request body is parsed. DoPacket()
disconnects on a refused id this build implements and on any refused id
of 80 or higher, the range RFC 4252 section 6 names; a refused id below
80 is answered UNIMPLEMENTED, RFC 4253 section 11.4.

- MsgIdKnown() carries DoPacket()'s dispatch ids, build guards included
- answer a refused id off the dispatch, so one cannot reach a handler
  if MsgIdKnown() falls behind
- skip the disconnect once the session is over, RFC 4253 section 11.1
  forbids sending after one
- cover ids 53, 79, 200 and a channel open before user auth on one
  keyed-server helper
- cover a client refusing both request names, with a reply asked for and
  without, and a server still succeeding

Issue: #1047 (3), #1047 (7), F-10576, F-10581, F-12574
2026-09-01 10:51:48 -05:00
John Safranek 9cedb5a1e7 Keep queued replies with the folded forward
A port-0 reply naming a port another registration stands for merges the
two, since one bind gets one registration. The requests still queued
against the entry that goes named that same bind, so FwdReplyRebind()
hands them to the survivor. Left naming nothing, a cancel among them
settles no forward and the merged one keeps matching opens.

- FwdReplyRebind() repoints queued slots before the stale entry is
  unlinked and FwdReplyVoid() clears them
- Test folds a port-0 reply onto a forward with a cancel already
  queued, and checks the cancel takes the survivor down
2026-08-31 20:35:56 -05:00
John Safranek a88404cb88 Drop a forward the peer refused twice
A cancel refusal leaves the forward standing, since the peer keeps a
listener it would not drop. There is no listener to keep when the setup
was refused too, so FwdRemoteSettle() unwinds a registration nothing
establishes and nothing is still owed an answer on, rather than leaving
it unconfirmed and unmatchable until the session ends.

- A cancel refusal unlinks an unconfirmed forward with no setup queued
- Test refuses a want-reply setup and the cancel behind it, and checks
  the registration is gone
2026-08-31 20:35:56 -05:00
John Safranek f6126898e0 Test a reply landing before the request commits
The mid-send window closed to the highwater callback when the commit
moved ahead of it, and the tests written for that window went with it:
they now answer a settled slot and reach the ordinary path. The IO send
callback is where a peer's answer can still land while the sender owns
its slot, so drive the reply from there.

- FwdReplyFromSendCb() answers the request from inside the flush, with
  a short-write and a fail-next mode for a send that loses the rest of
  the request after the answer is in.
- Tests cover a parked success, a parked refusal, a parked port-0 reply,
  a failed send giving the answered slot back, and an answer settling an
  earlier request while a cancel naming the same bind is mid-send.
- The highwater tests stay, for the committed path they now cover; their
  comments said they held an uncommitted slot.
2026-08-31 20:35:56 -05:00
John Safranek 8c920a8767 Commit forward state before the post-send callback
The forward a "tcpip-forward" or "cancel-tcpip-forward" establishes was
committed after SendGlobalRequestFwd() returned, which is after the
post-send highwater callback had run. A request that callback sends goes
out behind this one but committed ahead of it, so the earlier request
had the last word and the client ended up on the opposite side of the
forward from the peer. A first setup whose callback cancels it left the
forward registered with no listener on the peer, and a cancel whose
callback re-establishes the forward unlinked it, refusing every open for
a listener the peer holds.

- Split the post-send highwater check off wolfSSH_SendPacket() as
  SendPacketFlush(), for a sender with state to commit first.
- SendGlobalRequestFwd() takes the pending forward and settles it inside
  the send window, then runs the check.
- Commit order is send order now, so the last request sent governs,
  whichever call made it.
- FwdPendingCommit() still re-resolves the entry: the IO send callback
  can reenter mid-flush, which no ordering fixes.
- Tests cover a reentrant cancel of a first setup, a reentrant setup
  during a cancel, and an inbound forwarded-tcpip open pumped from the
  callback.
2026-08-31 20:35:56 -05:00
John Safranek cabbc53f8f Keep a rekey out of the mem-IO test harnesses
Filling the reply queue sends enough to cross a lowered highwater mark.
The rekey that fires sends a KEXINIT the 256-byte mem buffer cannot hold.

- Disable the highwater; a bigger buffer only moves the cliff.
- Both harnesses, since the buffer is the same size on each.
2026-08-31 20:35:56 -05:00
John Safranek 69bfbccbf9 Add a match setting for remote forwards
The forwarded-tcpip check is a behaviour change on a shipped API, so an
application that trips over it needs a way out that isn't abandoning
wolfSSH_FwdRemoteSetup(). wolfSSH_SetFwdRemoteMatch() relaxes the check
for the session.

- STRICT is the default and keeps the bind-plus-port rule.
- PORT compares the port alone, for a peer that rewrites the bind
  address it echoes back, which STRICT refuses every open from.
- OFF accepts any open, as wolfSSH did before the check existed.
- Tests cover each setting, and a refused one leaving the default in
  place.
2026-08-31 20:35:56 -05:00
John Safranek 3267d87bf5 Cap the pending-reply queue
Only the peer gives a slot back, by answering, so a peer that never
answers a want-reply global request lets the queue grow for the life of
the session, and the queue scans run once per forward on every inbound
forwarded-tcpip open.

- WOLFSSH_MAX_FWD_REPLIES bounds the queue.
- Refuse before the request is framed: one whose slot was never queued
  would mispair every later reply.
- Return WS_RESOURCE_E, not the WS_MEMORY_E that nothing failing to
  allocate would have yielded.
- Test fills the queue, then covers the refusal, that a refused setup
  registers nothing, and a slot coming back on an answer.
2026-08-31 20:35:56 -05:00
John Safranek 4ec4825d86 Let the reply-queue scans see a slot still in its send window
A slot named its forward only once its request committed, so the scans
could not see a request mid-send. A cancel the peer confirms while a
fresh setup for the same bind is still going out then found nothing
standing for the forward and unlinked it, leaving the peer with a
listener the client refuses every open for.

- Name a slot by its bind until it commits.
- FwdReplyNames() answers for both FwdReplyHasSetup() and
  FwdReplyNewest().
- The bind is borrowed from the caller and dropped at commit, the
  lifetime WOLFSSH_FWD_PENDING already assumes for it.
- A pointer to the entry would not do: the send runs callbacks that can
  free and remake it, which is why the entry is re-resolved at commit.
- Test drives a confirmed cancel against a re-setup still in its send
  window.
2026-08-31 20:35:56 -05:00
John Safranek 8d3f20bfde Fix interrupted and failed sends in SendPacket
A signal was turned into a fatal error, and a refused send left its
packet counted in the output buffer. Both fixes are in
wolfSSH_SendPacket(), so they cover every sender.

- WS_CBIO_ERR_ISR fell through to WS_SOCKET_ERROR_E. Nothing went out
  and the session is unharmed, so retry, as ReceiveData() already does.
- Callers that discard a packet on error, like the KEX and userauth
  sends, were throwing away framed output the peer never refused.
- On WS_CBIO_ERR_GENERAL the buffer was shrunk with the packet still
  counted in plainSz, so SendChannelData() flushed nothing and called it
  a success. Clear it with the packet it described.
- Tests pin the retry from a forwarding sender and a plain global
  request, and drive a channel send through a would-block and a refused
  flush.
2026-08-31 20:35:56 -05:00