Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions examples/echoserver/echoserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,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;

WMEMSET(name, 0, sizeof(struct sockaddr_un));
ctx->pid = getpid();
Expand All @@ -391,33 +391,37 @@ 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) {
if (ret > 0) {
name->sun_path[sizeof(name->sun_path) - 1] = '\0';
size = WSTRLEN(name->sun_path) +
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) {
ret = bind(ctx->listenFd,
(struct sockaddr *)name, (socklen_t)size);
ret = bind(ctx->listenFd, (struct sockaddr *)name,
(socklen_t)sizeof(struct sockaddr_un));
}
Comment on lines 400 to 403
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The bind() length was changed to sizeof(struct sockaddr_un), but elsewhere (e.g. the client-side agent code) the code computes strlen(path) + offsetof(sockaddr_un, sun_path). Using the full struct size can be less portable across UNIX variants; consider restoring the computed length here for consistency and compatibility.

Copilot uses AI. Check for mistakes.

if (ret == 0) {
ret = setenv(EnvNameAuthPort, name->sun_path, 1);
}

if (ret == 0) {
envSet = 1;
ret = listen(ctx->listenFd, 5);
}

if (ret == 0) {
ctx->state = AGENT_STATE_LISTEN;
}
else {
if (envSet) {
unsetenv(EnvNameAuthPort);
}
if (ctx->listenFd >= 0) {
close(ctx->listenFd);
ctx->listenFd = -1;
}
Comment on lines +418 to +424
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

If bind() succeeds but setenv() or listen() fails, the setup error path closes the FD and unsets the env var, but it does not remove the already-created UNIX socket pathname. Add an unlink(ctx->name.sun_path) (or unlink(name->sun_path)) in the failure cleanup to avoid leaving stale /tmp/wolfserver.<pid> sockets behind.

Copilot uses AI. Check for mistakes.
ret = WS_AGENT_SETUP_E;
}
}
Expand Down Expand Up @@ -2749,7 +2753,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");
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Hyphenation in this user-facing message is inconsistent with the common term “non-blocking”. Update the wording to “forced non-blocking”.

Suggested change
ES_ERROR("Use -N when testing forced non blocking\n");
ES_ERROR("Use -N when testing forced non-blocking\n");

Copilot uses AI. Check for mistakes.
}
#endif

Expand Down
22 changes: 21 additions & 1 deletion examples/portfwd/portfwd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -404,6 +409,21 @@ 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 = WSNPRINTF(portStr, sizeof(portStr), "%d\n", (int)port);
WFWRITE(NULL, portStr, MIN((size_t)l, sizeof(portStr)), 1, f);
Comment on lines +418 to +419
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

The ready-file writer is currently formatting/writing port (the SSH server port) rather than the local forwarded-from port that clients would need (likely fwdFromPort, which may be updated by tcp_listen() when binding ephemeral ports). Also, WSNPRINTF() can return a negative value on error; casting that to size_t can cause WFWRITE() to write an unintended length. Write the correct port value and guard against l <= 0 (and ideally handle a truncated result).

Suggested change
int l = WSNPRINTF(portStr, sizeof(portStr), "%d\n", (int)port);
WFWRITE(NULL, portStr, MIN((size_t)l, sizeof(portStr)), 1, f);
int l = WSNPRINTF(portStr, sizeof(portStr), "%d\n", (int)fwdFromPort);
if (l > 0) {
size_t writeLen = (size_t)l;
if (writeLen >= sizeof(portStr)) {
writeLen = sizeof(portStr) - 1;
}
WFWRITE(NULL, portStr, writeLen, 1, f);
}

Copilot uses AI. Check for mistakes.
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);
Expand Down
49 changes: 49 additions & 0 deletions scripts/fwd.test
Original file line number Diff line number Diff line change
@@ -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
154 changes: 154 additions & 0 deletions scripts/fwd.test.expect
Original file line number Diff line number Diff line change
@@ -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 fugat 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
2 changes: 1 addition & 1 deletion scripts/include.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ 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
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

scripts/fwd.test depends on scripts/fwd.test.expect, but include.am only adds scripts/fwd.test to dist_noinst_SCRIPTS. This will break make distcheck / running tests from a release tarball because the .expect file won’t be distributed. Add scripts/fwd.test.expect to the dist list (or another appropriate dist_* variable) alongside scripts/fwd.test.

Suggested change
dist_noinst_SCRIPTS+= scripts/external.test scripts/fwd.test
dist_noinst_SCRIPTS+= scripts/external.test scripts/fwd.test scripts/fwd.test.expect

Copilot uses AI. Check for mistakes.
2 changes: 2 additions & 0 deletions src/agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down