From 5371e5e9269db8de579c022d6e0b192b3aed2be7 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Mon, 2 Feb 2026 11:11:32 -0800 Subject: [PATCH 1/3] RxD Fix 1. When wolfSSH_worker() receives channel data, it should set the channelId for the data. It was not happening. Change the check for WS_SUCCESS to also check for WS_CHAN_RXD. --- src/ssh.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ssh.c b/src/ssh.c index 1506987e0..afebd0ae0 100644 --- a/src/ssh.c +++ b/src/ssh.c @@ -2607,7 +2607,7 @@ int wolfSSH_worker(WOLFSSH* ssh, word32* channelId) } #endif /* WOLFSSH_TEST_BLOCK */ - if (ret == WS_SUCCESS) { + if (ret == WS_SUCCESS || ret == WS_CHAN_RXD) { if (channelId != NULL) { *channelId = ssh->lastRxId; } From 83250df7314e45ef0e79a89d33b134ab6e229e82 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Tue, 3 Feb 2026 19:52:00 -0800 Subject: [PATCH 2/3] Agent Update 1. Fix a couple unused variable warnings. 2. In wolfSSH_AGENT_DefaultActions(), fix comparison to the result of snprintf() treating normal result as an error. Reset the return code for the error state of the socket() command. Better cleanup of agent startup failures. --- examples/echoserver/echoserver.c | 26 ++++++++++++++++++++------ examples/portfwd/portfwd.c | 19 +++++++++++++++++++ src/agent.c | 2 ++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index cbf2662b4..97b4a61a2 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -383,6 +383,7 @@ static int wolfSSH_AGENT_DefaultActions(WS_AgentCbAction action, void* vCtx) if (action == WOLFSSH_AGENT_LOCAL_SETUP) { struct sockaddr_un* name = &ctx->name; size_t size; + int envSet = 0, nameBound = 0; WMEMSET(name, 0, sizeof(struct sockaddr_un)); ctx->pid = getpid(); @@ -390,15 +391,16 @@ static int wolfSSH_AGENT_DefaultActions(WS_AgentCbAction action, void* vCtx) ret = snprintf(name->sun_path, sizeof(name->sun_path), "/tmp/wolfserver.%d", ctx->pid); + if (ret >= 0) { + name->sun_path[sizeof(name->sun_path) - 1] = '\0'; + size = WSTRLEN(name->sun_path); + ret = (size < WSTRLEN("/tmp/wolfserver.")); + } if (ret == 0) { - name->sun_path[sizeof(name->sun_path) - 1] = '\0'; - size = WSTRLEN(name->sun_path) + - offsetof(struct sockaddr_un, sun_path); + size += offsetof(struct sockaddr_un, sun_path); ctx->listenFd = socket(AF_UNIX, SOCK_STREAM, 0); - if (ctx->listenFd == -1) { - ret = -1; - } + ret = (ctx->listenFd == -1) ? -1 : 0; } if (ret == 0) { @@ -407,10 +409,12 @@ static int wolfSSH_AGENT_DefaultActions(WS_AgentCbAction action, void* vCtx) } if (ret == 0) { + nameBound = 1; ret = setenv(EnvNameAuthPort, name->sun_path, 1); } if (ret == 0) { + envSet = 1; ret = listen(ctx->listenFd, 5); } @@ -418,6 +422,16 @@ static int wolfSSH_AGENT_DefaultActions(WS_AgentCbAction action, void* vCtx) ctx->state = AGENT_STATE_LISTEN; } else { + if (nameBound) { + unlink(ctx->name.sun_path); + } + if (envSet) { + unsetenv(EnvNameAuthPort); + } + if (ctx->listenFd >= 0) { + close(ctx->listenFd); + ctx->listenFd = -1; + } ret = WS_AGENT_SETUP_E; } } diff --git a/examples/portfwd/portfwd.c b/examples/portfwd/portfwd.c index 723d1ba2c..ab7b37174 100644 --- a/examples/portfwd/portfwd.c +++ b/examples/portfwd/portfwd.c @@ -404,6 +404,25 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args) if (ret != WS_SUCCESS) err_sys("Couldn't connect SFTP"); + if (readyFile != NULL) { + #ifndef NO_FILESYSTEM + WFILE* f = NULL; + ret = WFOPEN(NULL, &f, readyFile, "w"); + if (f != NULL && ret == 0) { + char portStr[10]; + int l; + + l = WSNPRINTF(portStr, sizeof(portStr), "%d\n", (int)fwdFromPort); + if (l > 0) { + WFWRITE(NULL, portStr, MIN((size_t)l, sizeof(portStr)), 1, f); + WFCLOSE(NULL, f); + } + } + #else + err_sys("cannot create readyFile with no file system.\r\n"); + #endif + } + FD_ZERO(&templateFds); FD_SET(sshFd, &templateFds); FD_SET(listenFd, &templateFds); diff --git a/src/agent.c b/src/agent.c index 5cc160d64..dd5bb8782 100644 --- a/src/agent.c +++ b/src/agent.c @@ -374,6 +374,7 @@ static int PostLock(WOLFSSH_AGENT_CTX* agent, word32 ppSz; WLOG(WS_LOG_AGENT, "Posting lock to agent %p", agent); + WOLFSSH_UNUSED(agent); ppSz = sizeof(pp) - 1; if (passphraseSz < ppSz) @@ -395,6 +396,7 @@ static int PostUnlock(WOLFSSH_AGENT_CTX* agent, word32 ppSz; WLOG(WS_LOG_AGENT, "Posting unlock to agent %p", agent); + WOLFSSH_UNUSED(agent); ppSz = sizeof(pp) - 1; if (passphraseSz < ppSz) From 62b9ae738bc8c36c1ac637f9fd80b3dda9e138b2 Mon Sep 17 00:00:00 2001 From: John Safranek Date: Tue, 24 Feb 2026 10:38:32 -0800 Subject: [PATCH 3/3] Forwarding Test 1. Add a test script and expect script for testing forwarding. 2. Update portfwd to have a ready file option. 3. Fix echoserver error string, needed NL. --- examples/echoserver/echoserver.c | 4 +- examples/portfwd/portfwd.c | 7 +- scripts/fwd.test | 49 ++++++++++ scripts/fwd.test.expect | 154 +++++++++++++++++++++++++++++++ scripts/include.am | 3 +- 5 files changed, 213 insertions(+), 4 deletions(-) create mode 100755 scripts/fwd.test create mode 100755 scripts/fwd.test.expect diff --git a/examples/echoserver/echoserver.c b/examples/echoserver/echoserver.c index 97b4a61a2..b5cb75da4 100644 --- a/examples/echoserver/echoserver.c +++ b/examples/echoserver/echoserver.c @@ -1524,7 +1524,7 @@ static THREAD_RETURN WOLFSSH_THREAD server_worker(void* vArgs) if (!threadCtx->nonBlock) { ret = wolfSSH_accept(threadCtx->ssh); if (wolfSSH_get_error(threadCtx->ssh) == WS_AUTH_PENDING) { - printf("Auth pending error, use -N for non blocking\n"); + printf("Auth pending error, use -N for non-blocking\n"); printf("Trying to close down the connection\n"); } } @@ -2763,7 +2763,7 @@ THREAD_RETURN WOLFSSH_THREAD echoserver_test(void* args) #ifdef WOLFSSH_TEST_BLOCK if (!nonBlock) { - ES_ERROR("Use -N when testing forced non blocking"); + ES_ERROR("Use -N when testing forced non-blocking\n"); } #endif diff --git a/examples/portfwd/portfwd.c b/examples/portfwd/portfwd.c index ab7b37174..8b8a94d93 100644 --- a/examples/portfwd/portfwd.c +++ b/examples/portfwd/portfwd.c @@ -235,6 +235,7 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args) const char* fwdToHost = NULL; const char* username = NULL; const char* password = NULL; + const char* readyFile = NULL; SOCKADDR_IN_T hostAddr; socklen_t hostAddrSz = sizeof(hostAddr); SOCKET_T sshFd; @@ -266,7 +267,7 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args) ((func_args*)args)->return_code = 0; - while ((ch = mygetopt(argc, argv, "?f:h:p:t:u:F:P:T:")) != -1) { + while ((ch = mygetopt(argc, argv, "?f:h:p:t:u:F:P:R:T:")) != -1) { switch (ch) { case 'h': host = myoptarg; @@ -306,6 +307,10 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args) password = myoptarg; break; + case 'R': + readyFile = myoptarg; + break; + case 'T': fwdToHost = myoptarg; break; diff --git a/scripts/fwd.test b/scripts/fwd.test new file mode 100755 index 000000000..26034eb6e --- /dev/null +++ b/scripts/fwd.test @@ -0,0 +1,49 @@ +#!/usr/bin/env sh + +# Prerequisite checking + +if [ ! -x "$(which expect)" ] +then + echo "skipping: missing expect" + exit 77 +fi + +if [ ! -x ./scripts/fwd.test.expect ] +then + echo "fail: missing expect script" + exit 1 +fi + +if [ ! -x "$(which nc)" ] +then + echo "skipping: missing netcat" + exit 77 +fi + +## libtool can leave behind a script that runs the actual executable. +if [ ! -x ./examples/echoserver/echoserver ] || \ + ./examples/echoserver/echoserver "-?" 2>&1 | grep -q "does not exist" +then + echo "fail: missing echoserver" + exit 1 +fi + +## libtool can leave behind a script that runs the actual executable. +if [ ! -x ./examples/portfwd/portfwd ] || \ + ./examples/portfwd/portfwd "-?" 2>&1 | grep -q "does not exist" +then + echo "skipping: missing forwarding" + exit 77 +fi + +## test for nonblocking only +if ./examples/client/client "-?" 2>&1 | grep WOLFSSH_TEST_BLOCK >/dev/null 2>&1 +then + echo "skipping: non-blocking test" + exit 77 +fi + + +# Run the expect script + +./scripts/fwd.test.expect diff --git a/scripts/fwd.test.expect b/scripts/fwd.test.expect new file mode 100755 index 000000000..fdb5383e1 --- /dev/null +++ b/scripts/fwd.test.expect @@ -0,0 +1,154 @@ +#!/usr/bin/env expect -f +# +# SSH Tunnel Test Script +# +# Tests an SSH tunnel using wolfSSH and netcat (nc). +# +# Architecture: +# +# [nc client] --plain--> :12345 [wolfssh client] +# | +# SSH +# | +# [wolfssh server] :ephem --plain--> :11111 [nc server] +# +# The nc client sends each line of a Lorem Ipsum paragraph through the tunnel +# to the nc server one at a time. The server echoes each line back. Both sides +# verify receipt of every line. +# +# Ports used: +# 11111 - nc server (plain text backend) +# 12345 - wolfssh client listener (plain text, nc connects here) +# 22222 - wolfSSH rendezvous (SSH, internal use only) +# +# Requirements: nc (netcat), expect + +set timeout 30 + +set lorem_lines { + {Lorem ipsum dolor sit amet, consectetur adipiscing elit,} + {sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.} + {Ut enim ad minim veniam, quis nostrud exercitation ullamco} + {laboris nisi ut aliquip ex ea commodo consequat.} + {Duis aute irure dolor in reprehenderit in voluptate velit esse} + {cillum dolore eu fugiat nulla pariatur.} + {Excepteur sint occaecat cupidatat non proident, sunt in culpa qui} + {officia deserunt mollit anim id est laborum.} +} + +# PIDs for cleanup +set nc_server_pid "" +set wolfssh_srv_pid "" +set wolfssh_clt_pid "" +set nc_client_pid "" + +# --- Cleanup ----------------------------------------------------------------- +proc cleanup {} { + global nc_client_pid wolfssh_clt_pid wolfssh_srv_pid nc_server_pid + + puts "\n--- Cleaning up ---" + foreach pid [list $nc_client_pid $wolfssh_clt_pid $wolfssh_srv_pid $nc_server_pid] { + if {$pid ne ""} { + catch {exec kill $pid} + } + } + puts "Done." +} + +# --- Fail helper ------------------------------------------------------------- +proc fail {msg} { + puts "\n\[FAIL\] $msg" + cleanup + exit 1 +} + +# --- Check prerequisites ----------------------------------------------------- +foreach tool {nc} { + if {[catch {exec which $tool}]} { + puts "ERROR: '$tool' not found in PATH" + exit 1 + } +} + +# --- [1] Start nc server ----------------------------------------------------- +puts "\n\[1\] Starting nc server: nc -l 11111" +spawn nc -l 11111 +set nc_server_id $spawn_id +set nc_server_pid [exp_pid] +puts " PID $nc_server_pid — waiting for a connection..." + +# --- [2] Start wolfssh server ------------------------------------------------ +puts "\n\[2\] Starting wolfssh server..." +spawn ./examples/echoserver/echoserver -1 -f +set wolfssh_srv_id $spawn_id +set wolfssh_srv_pid [exp_pid] +puts " PID $wolfssh_srv_pid — waiting for a connection..." + +# --- [3] Start wolfssh client ------------------------------------------------ +puts "\n\[3\] Starting wolfssh client (plain:12345 -> 11111)..." +spawn ./examples/portfwd/portfwd -u jill -P upthehill -f 12345 -t 11111 +set wolfssh_clt_id $spawn_id +set wolfssh_clt_pid [exp_pid] + +expect { + -i $wolfssh_clt_id + -re {sampled} { + puts " wolfssh client ready (PID $wolfssh_clt_pid)." + } + -re {(?i)(error|fatal)} { + fail "wolfssh client failed to start" + } + timeout { + fail "Timed out waiting for wolfssh client to start" + } +} + +# Brief pause to let the wolfssh tunnels fully bind their listening ports +sleep 1 + +# --- [4] Start nc client ----------------------------------------------------- +puts "\n\[4\] Starting nc client: nc localhost 12345" +spawn nc localhost 12345 +set nc_client_id $spawn_id +set nc_client_pid [exp_pid] +puts " PID $nc_client_pid" + +# Allow the TCP handshake and SSH negotiation to complete +sleep 1 + +# --- [5] Send each line, verify receipt, echo back, verify echo -------------- +set n [llength $lorem_lines] +set i 0 +foreach line $lorem_lines { + incr i + puts "\n\[5.$i/$n\] Client sending: \"$line\"" + send -i $nc_client_id "$line\n" + + expect { + -i $nc_server_id + -ex $line { + puts " \[PASS\] Server received line $i." + } + timeout { + fail "Server did not receive line $i within ${timeout}s" + } + } + + send -i $nc_server_id "$line\n" + + expect { + -i $nc_client_id + -ex $line { + puts " \[PASS\] Client received echo of line $i." + } + timeout { + fail "Client did not receive echo of line $i within ${timeout}s" + } + } +} + +# --- Done -------------------------------------------------------------------- +puts "\n=== TEST PASSED ===\n" + +cleanup +exit 0 diff --git a/scripts/include.am b/scripts/include.am index 02f4d943f..2f7693625 100644 --- a/scripts/include.am +++ b/scripts/include.am @@ -11,4 +11,5 @@ if BUILD_SCP dist_noinst_SCRIPTS+= scripts/scp.test endif -dist_noinst_SCRIPTS+= scripts/external.test +dist_noinst_SCRIPTS+= scripts/external.test scripts/fwd.test +EXTRA_DIST += scripts/fwd.test.expect