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
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.
- The WOLFSSH_ZEPHYR branch of FindNextDirEntry() loops while the
entry name is "." or "..", matching the POSIX and Windows
branches, in place of while (1).
- tests/api.c gains test_wolfSSH_SCP_SendRecursiveEntry(), staging a
directory holding one file and driving three
WOLFSSH_SCP_RECURSIVE_REQUEST calls through wsScpSendCallback(),
checking the entry name, size, and bytes placed in buf. dirPath
is rooted at CONFIG_WOLFSSH_SFTP_DEFAULT_DIR under WOLFSSH_ZEPHYR
and at "./scp_recur_entry" otherwise.
- The test is gated on WOLFSSH_SCP, with WOLFSSH_SCP_USER_CALLBACKS,
NO_FILESYSTEM and NO_WOLFSSH_DIR unset, carries an empty stub
otherwise, and is called from wolfSSH_ApiTest().
- scpStageRecurFile() writes that fixture file.
Issue: F-13315
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
- 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
- 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
RFC 4254 7.2 says a forwarded-tcpip open answers a forward the client
asked for, so refuse an open naming anything else. Enforcement starts at
the first wolfSSH_FwdRemoteSetup(), leaving a client that frames
tcpip-forward itself unaffected.
- Register each setup per session. A wildcard bind matches on port alone.
- Port 0 now requires want-reply, since only the reply names the port.
- Repeat setups of one bind share a registration, so one cancel undoes
it.
- A cancel stops matching as it goes out, but a want-reply cancel stays
registered until the peer answers: a refusal leaves the listener up.
- Replies carry no request id, so a per-session queue pairs them in send
order. A want-reply wolfSSH_global_request() takes a slot as well.
- Registration is split around the send: allocate first, link once the
request reached the wire.
- A request resolves its registration on commit, since sending runs
application callbacks that can reenter the library.
- Tests cover matching, cancel, overlapping requests, send-order
pairing, port 0, registration around the send, and reentrancy from a
callback.
- They drive a client session, so they sit outside the server-only block
in regress.c and run in a --disable-server build. The harness struct,
its teardown, the channel-open-failure helpers and the forwarding
callback moved out with them, shared with the server-side tests.
Contracts for wolfSSH_FwdRemoteSetup(), wolfSSH_FwdRemoteCancel() and
wolfSSH_global_request() are in wolfssh/ssh.h.
Issue: ZD-22195
RFC 4256 section 3.3 forbids an empty prompt, and RFC 4251 section 5
allows only 0 or 1 in a boolean field. Validation runs before the
payload is sized, so the sizing and building passes agree.
- reject a zero-length prompt or one with a NULL buffer
- reject a prompt count with the arrays unset
- reject a prompt over WOLFSSH_MAX_PROMPT_SZ, which the sizing pass
would otherwise sum into a wrapped payload size
- normalize the outgoing echo byte to 0 or 1
- cover the rejections in tests/api.c, the echo byte in tests/auth.c
Issue: F-10583, F-10584
The refusals added for names the peer cannot use changed the return
contract of a public API whose block comment still promised only
WS_SUCCESS. There is no dox_comments entry, so that comment is all an
embedder has.
- Spell out each WS_BAD_ARGUMENT case, the keep-the-stored-name rule,
and that a refused call leaves the selected type alone.
- api.c asserts connectChannelId across the refusals. It is the field
SendChannelRequest() switches on, so moving the checks back below the
assignment would otherwise pass.
wolfSSH_SetChannelType() discarded an exec or subsystem name it could
not use and still returned WS_SUCCESS. SendChannelRequest() then omits
the name field entirely, which the peer rejects as malformed, dropping
the connection. Both an oversized name and an empty one reach it; the
empty case is reachable from the command line as "wolfssh -c ''".
- Return WS_BAD_ARGUMENT for a name at or above WOLFSSH_MAX_CHN_NAMESZ,
matching how the function already reports a bad type or side.
- Return WS_BAD_ARGUMENT when no name is given and none was stored by
an earlier call, and when a size arrives with no name behind it.
- Keep returning WS_SUCCESS when an earlier call stored a name, which
is what the SFTP and SCP retry loops depend on.
- Return before setting connectChannelId so a rejected call leaves
no state behind, as the server-side exec rejection does.
- Keep the stored name intact when a later call is refused.
- api.c asserts each refusal, and the largest name still admitted.
MAX_PACKET_SZ caps the whole SSH binary packet, but the channel
maxPacketSz it was compared against counts only channel payload. A
peer honoring the advertised 35000 overruns the receiver's own check.
- Derive MAX_CHANNEL_PACKET_SZ in internal.h: MAX_PACKET_SZ less the
transport framing, the CHANNEL_EXTENDED_DATA header, the worst-case
padding BundlePacket() picks, and MAX_HMAC_SZ. 34899 by default.
- Name that overhead twice, once for the compiler and once as a
literal for the preprocessor, which reads the wolfCrypt enum
constants in the first form as zero. The #error guarding
DEFAULT_MAX_PACKET_SZ uses the second rather than its own copy.
- MAX_CHANNEL_PACKET_SZ is derived rather than a tunable, so it is
not overridable; an override defeated the bound it enforces.
- wolfSSH_CTX_SetWindowPacketSize() bounds maxPacketSz against that
instead of MAX_PACKET_SZ; DEFAULT_MAX_PACKET_SZ is unaffected.
- api.c tests the new edge and that MAX_PACKET_SZ is now rejected.
Issue: F-8835
- GetOpenSshPublicKey() calls NameToId() and enters the key-type
switch only when GetStringRef() returns WS_SUCCESS, and returns
that result otherwise.
- publicKeyType starts NULL and keyId starts ID_UNKNOWN.
- tests/api.c adds test_GetOpenSshPublicKey_type(), gated on
WOLFSSH_TPM and WOLFSSH_TEST_INTERNAL, covering a truncated type
string, a truncated length prefix, an empty type, an unsupported
type and a well-formed ssh-rsa blob.
- Each case asserts idx alongside the return code: UINT32_SZ for a
truncated type string, 0 for a truncated length prefix, and the
full blob size for the empty type, the unsupported type and the
ssh-rsa key.
Issue: F-11650
- wolfSSH_SFTP_Put() adds WOLFSSH_FXF_TRUNC to the destination open
only when the write offset is zero.
- STATE_PUT_LOOKUP_OFFSET clears a saved offset when the local file
is no larger than it.
- A new STATE_PUT_STAT_REMOTE stats the destination when the saved
offset is nonzero, and clears the offset unless the reported size
matches it exactly, or the stat returns WS_SFTP_STATUS_NOT_OK or
WS_PERMISSIONS. Other stat failures re-save the offset and move to
STATE_PUT_CLEANUP; a want-read or want-write keeps the state.
WS_SFTP_PUT_STATE carries the attributes both states read.
- The Windows server open maps WOLFSSH_FXF_CREAT to OPEN_ALWAYS and
reserves CREATE_ALWAYS for an open that also asked for
WOLFSSH_FXF_TRUNC; the disabled TRUNCATE_EXISTING mapping is
dropped.
- tests/api.c adds test_wolfSSH_SFTP_PutResume(), five cases over the
resume paths, built where the hosted file wrappers are available.
Issue: F-11659
- Add wolfSSH_SFTP_SetConfinePath and a separate sftpConfinePath, so
the start path only says where a session begins, and only an
explicit confinement root rejects out-of-tree requests.
- Have GetAndCleanPath take the WOLFSSH and enforce the confinement
root, resolving relative requests against the start path.
- Factor the shared canonicalize-and-store work out of
wolfSSH_SFTP_SetDefaultPath into CanonicalizePath and StorePath.
- Give the echoserver -D to opt a session into confinement; without
it the -d home directory only says where SFTP starts.
- Document both settings, and the symlink and TOCTOU caveats, once in
wolfsftp.h, noting the confinement root itself is trusted.
- Cover the split in api.c, and in regress.c start a session in a
subdirectory of the confinement root: a sibling of the start
directory is reachable, anything above the root is not.
Issue: ZD-22308
- PostSignRequest() allocates the signature buffer from agent->heap,
sized from the identity's modulus mpint for RSA and from
ECDSA_ASN_SIG_SZ for ECDSA, and frees it before returning. A modulus
longer than RSA_MAX_SIZE, or a key type that sets no size, returns
WS_BUFFER_E.
- wolfSSH_AGENT_SignRequest() reads the agent's reply into a heap
buffer of WOLFSSH_AGENT_MAX_RSP_SZ, a new overridable define in
agent.c, freed after the last use of agent->msg.
- tests/api.c carries a 3072-bit RSA key and a P-521 key as hex string
components, and build_string() and build_mpint() helpers that write
the message fields. AgentTestCtx.response sizes from
AGENT_TEST_BUF_SZ.
- test_wolfSSH_agent_signrequest_rsa_3072() and
test_wolfSSH_agent_signrequest_ecc_p521() add their identity through
the agent callbacks and sign with it, then clear the stored private
exponent or point and sign again.
test_wolfSSH_agent_signrequest_rsa_too_large() adds an identity whose
modulus exceeds RSA_MAX_SIZE and expects WS_BUFFER_E.
- The comment on test_wolfSSH_agent_signrequest_oversize_rsa_key()
describes the identity that test uses.
Issue: F-10541
- LoadRootCaPemBuffer() loads every block a PEM CA buffer holds as a
root CA, skipping the ones that fail. It returns WS_SUCCESS when
any loaded, WS_PARSE_E when all failed, and WS_BAD_FILE_E when the
buffer holds no block.
- A block runs header to footer with the next header capping the
footer search, so wc_PemToDer() gets the block rather than the rest
of the buffer. A header that nothing closes is skipped and the walk
resumes at its end; each form's header is re-sought only from
behind the one just read.
- A block takes the plain or the trusted form, whichever header leads
picking the type. FindInBuffer() searches a length-delimited
buffer, so an embedded NUL does not end the search.
- wolfSSH_ProcessBuffer() routes a PEM BUFTYPE_CA there and, like
DoPemCert(), gives WS_BAD_FILETYPE_E for the trusted form as a
certificate; SniffCertForm() reads its header as X.509 PEM.
- internal.h defines WOLFSSH_HAVE_TRUSTED_CERT_PEM under
WOLFSSH_CERTS with wolfSSL 5.8.0 or newer and declares
IsTrustedCertPem(); ssh.h documents the cert buffer calls.
- tests/api.c adds catBuffers(), makeTrustedPem() and
assertCaInstalled(), with tests for the bundle, trusted file and
trusted ReadCert paths.
- test.h's static Base16_Decode collides with wolfSSL's public one
when coding.h lands first, breaking --enable-tpm builds.
- Include coding.h in test.h, keeping the local copy only when
WOLFSSL_BASE16 is absent; --enable-wolfssh alone does not set it.
- api.c includes coding.h too, dropping its hand-declared
Base64_Encode_NoNl, which would now be a duplicate.
- add sftp_retry_remove()/sftp_retry_close() so Close and Remove are
driven to completion instead of abandoned mid-operation
- split the retry predicate: Open and SendWritePacket keep state only on
WS_WANT_READ/WS_WANT_WRITE, LS adds WS_REKEYING, Close and Remove gate
on NoticeError()
- assert the staging close; keep the post-read cleanup best effort, as
the reads are skipped rather than completed on WS_REKEYING
- route the WOLFSSH_TEST_INTERNAL write cleanup through the helpers
- shrink the staging chunk to 512 bytes
- Clamp the staging write chunk to the bytes remaining and derive the
try cap from the chunk count, so the loop lands exactly on
WOLFSSH_MAX_SFTP_RW for any value of the macro.
- Retry the staging Open and the LS on WS_WANT_READ, WS_WANT_WRITE,
and WS_REKEYING, matching the rekey tolerance used elsewhere in the
function.
- Assert the staging Open, the LS, and the listing match rather than
keying the read block on them, so neither a failed create nor a
connection stuck in WANT/REKEYING until the try cap can drop the
read coverage without failing the test.
- Remove any stale file before the staging Open, so the reads never
target a file staged by a prior aborted run.
- Size the read buffer from WOLFSSH_MAX_SFTP_RW, the amount staged and
the largest amount read, instead of the listed file size, and drop
the listed-size guard on the 18 byte read.
- Guard the listing name compare against a NULL fName.
- Verify every read returns the staged fill byte, not just a valid
length; the check is a macro so a failure reports the calling
read's line.
- Opening the listing's first entry raced with tests/testsuite.test,
which creates and removes files in the same directory under
"make -j check"; the entry could be gone before the open.
- Stage the read target over SFTP, select it from the listing by name,
and remove it afterward.
- Check the listing with AssertNotNull; a missing entry used to skip
the whole test body.
- Track the scpFileName allocation size in a new scpFileNameCap field,
so GetScpFileName() and ScpCheckForRename() reuse the buffer whenever
the name plus its terminator fits, instead of testing against the
previous name length
- Pass scpFileNameCap to the send callback, which writes into
scpFileName and needs the capacity; scpFileNameSz is now the name
length on every path, including the source path
- Free and reallocate the transfer buffer in ScpSourceInit(), clearing
the size fields alongside the pointers they describe
- Wrap a long line in ScpProcessEntry()
- Add test_ScpGetFileName covering the reuse-vs-realloc branch, the
exact-fit boundary, a grow-by-one that catches an off-by-one in the
reuse condition, and a source-path buffer holding no name yet
- Add test_wolfSSH_SCP_RecursiveTwoFiles, a real "scp -r" transfer of
two files, covering the scpFileHeaderSent reset on the recursive
path added in the duplicate-header fix; it clears leftovers from an
aborted run up front and bounds its recv so a regression fails
instead of hanging. Not built on Windows, where a recursive transfer
through the default callbacks does not reproduce the sent file
- Gate header on new scpFileHeaderSent flag, not scpFileOffset==0
- A send callback returning 0 bytes first no longer re-sends header
- Skip zero-length SCP_SEND_FILE send to avoid empty CHANNEL_DATA
- Abort on a second consecutive 0-byte send callback return with file
data outstanding; skipping the send would otherwise spin
SCP_SEND_FILE -> SCP_TRANSFER with no socket I/O
- Reset offset/bufferedSz/flags in ScpSourceInit for connection reuse
- Document the WS_CallbackScpSend contract, including when a 0 return
is valid, next to the typedef
- Add test_wolfSSH_SCP_SendZeroFirst regression (func_args scp_send hook)
- Zero func_args in kex.c/testsuite.c so the new scp_send field is not read uninitialized
Issue: ZD-22176
- IdentifyAsn1Key() was called with a literal 1, so a public PEM decoded
by wc_PubKeyPemToDer() was run through the private-key decoders and
never identified.
- Add test_wolfSSH_ReadPublicKey_pem() covering a public RSA PEM read
through wolfSSH_ReadPublicKey_buffer(). The read itself only compiles
with WOLFSSH_TPM, so the test also asserts the isPrivate 0 vs 1
difference in IdentifyAsn1Key() directly, which every build runs.
Issue: F-7209
c[inSz-1] = 0 clobbered the last byte of the copied key data instead
of terminating the string after it, truncating public keys by one
character.
Issue: F-7205
- Add wolfSSH_FwdRemoteSetup/Cancel to request tcpip-forward
- Add SendGlobalRequestFwd to frame the request per RFC 4254
- Add portfwd -r reverse mode, driven by the fwd and req-success cbs
- Wait for the peer's reply before -R reports ready, and take the
bound port from it; with -f 0 that reply is the only source
- Accept -f 0 in reverse mode, where it asks the peer to pick the port
- Cancel names the port the peer bound, not the one requested, so a
peer keyed on its listener's port can still find it
- Add a req-failure cb so a refused forward is reported, not waited on
Issue: ZD-21867
- Charge abandoned keyboard INFO_REQUEST before method dispatch so a
peer can't dodge the cap; drop unused "counted" param.
- Exempt the opening "none" probe in DoUserAuthRequestNone() under
WOLFSSH_ALLOW_USERAUTH_NONE, matching ssh.h contract.
- unit.c: add keyboard restart, setup-rejection, and none-exemption
MaxAuthAttempts scenarios.
- api.c: assert "none" cipher/MAC accepted under WOLFSSH_ALLOW_NONE_CIPHER.
- Setters stored caller string verbatim; "none" cipher/MAC risked cleartext
- Add CheckAlgoList()/NameToIdType() to validate each token by category
- Set* now returns WS_INVALID_ALGO_ID on bad/NULL input, list unchanged
- "none" is rejected in the key list, and allowed for cipher/MAC only under
WOLFSSH_ALLOW_NONE_CIPHER
- Names unknown to the build are skipped rather than rejected, so a portable
superset list still works; a known name in the wrong category fails
- Reject empty list elements apart from one trailing comma, which would
otherwise reach KEXINIT as a zero-length name
- Give ID_NONE a block size so the none cipher no longer divides by zero in
BundlePacket(), and skip the zero-length key derivations it implies
- Treat an empty peer server-sig-algs as no extension rather than a fatal
parse error
- The "at least one X" guards checked the implementation macros but not the
soft-disable macros that gate joining the canned default list, so a build
whose only survivors are soft-disabled got an empty default list: KEXINIT
advertised nothing and CheckAlgoList() rejected it. Reachable as
-DWOLFSSH_NO_HMAC_SHA2_256 -DWOLFSSH_NO_HMAC_SHA2_512 and as
-DWOLFSSH_NO_AES_GCM -DWOLFSSH_NO_AES_CTR; both now fail at compile time
naming the soft-disable macro to define
- Zero MAC algorithms is already a hard #error, so assert the queried MAC
list in test_wolfSSH_SetAlgoList() rather than returning early, which was
hiding the KEX/KEY/CIPHER coverage behind an unreachable guard
Issue: 2474
Harden the partial-sequence reassembly in wolfSSH_DoControlSeq and fix an
escape-state leak in the wolfSSH_ConvertConsole OSC resume path
(USE_WINDOWS_API console handling).
- Tighten both escBuf partial-save branches to reject bufSz - *idx >=
WOLFSSL_MAX_ESCBUF before the WMEMCPY and log via WLOG. escBuf is
WOLFSSL_MAX_ESCBUF bytes, so a save of exactly that length fills it with
no room for later state; rejecting the boundary keeps the two branches
consistent and matches the resume check.
- Add the capacity guard to the initial-parse CSI save branch, which
previously copied into escBuf with no bound. In the default build getArgs
caps the advance below WOLFSSL_MAX_ESCBUF so the copy was already safe;
the guard prevents a custom WOLFSSH_MAX_CONSOLE_ARGS > WOLFSSL_MAX_ESCBUF
build from overflowing escBuf.
- Clear escState to WC_ESC_NONE when a resumed OSC sequence completes, not
just escBufSz. Without this escState stayed WS_ESC_OSC and the next call
re-entered OSC parsing on plain bytes instead of printing them.
Extend test_wolfSSH_ConvertConsole with a CSI sequence split across three
calls (ESC[ | args | command char), a trailing plain byte that must be
printed once state is cleared, and a single buffer whose CSI args run to
the end and complete on the next byte.