Skip to content
Draft
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
19 changes: 11 additions & 8 deletions examples/echoserver/echoserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,6 @@ static int ssh_worker(thread_ctx_t* threadCtx)
#endif
#ifdef WOLFSSH_FWD
WS_SOCKET_T fwdFd = -1;
WS_SOCKET_T fwdListenFd = threadCtx->fwdCtx.listenFd;
word32 fwdBufferIdx = 0;
#endif

Expand Down Expand Up @@ -955,10 +954,13 @@ static int ssh_worker(thread_ctx_t* threadCtx)
}
#endif /* WOLFSSH_AGENT */
#ifdef WOLFSSH_FWD
if (threadCtx->fwdCtx.state == APP_STATE_LISTEN) {
FD_SET(fwdListenFd, &readFds);
if (fwdListenFd > maxFd)
maxFd = fwdListenFd;
/* The fwd callback creates this listener mid-loop, so re-read
* it each pass instead of caching it. */
if (threadCtx->fwdCtx.state == APP_STATE_LISTEN
&& threadCtx->fwdCtx.listenFd >= 0) {
FD_SET(threadCtx->fwdCtx.listenFd, &readFds);
if (threadCtx->fwdCtx.listenFd > maxFd)
maxFd = threadCtx->fwdCtx.listenFd;
}
if (fwdFd >= 0
&& threadCtx->fwdCtx.state == APP_STATE_CONNECTED) {
Expand Down Expand Up @@ -1250,12 +1252,13 @@ static int ssh_worker(thread_ctx_t* threadCtx)
}
}
}
if (threadCtx->fwdCtx.state == APP_STATE_LISTEN) {
if (FD_ISSET(fwdListenFd, &readFds)) {
if (threadCtx->fwdCtx.state == APP_STATE_LISTEN
&& threadCtx->fwdCtx.listenFd >= 0) {
if (FD_ISSET(threadCtx->fwdCtx.listenFd, &readFds)) {
#ifdef SHELL_DEBUG
printf("accepting fwd connection\n");
#endif
fwdFd = accept(fwdListenFd, NULL, NULL);
fwdFd = accept(threadCtx->fwdCtx.listenFd, NULL, NULL);
if (fwdFd == -1) {
rc = errno;
if (rc != SOCKET_EWOULDBLOCK) {
Expand Down
214 changes: 201 additions & 13 deletions examples/portfwd/portfwd.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,10 @@ static void ShowUsage(void)
" -F <host> host to forward from, default %s\n"
" -f <num> host port to forward from (REQUIRED)\n"
" -T <host> host to forward to, default to host\n"
" -t <num> port to forward to (REQUIRED)\n",
" -t <num> port to forward to (REQUIRED)\n"
" -r remote (reverse) forward: ask the SSH server to\n"
" listen on -F/-f and tunnel connections back to\n"
" the local -T/-t target\n",
LIBWOLFSSH_VERSION_STRING,
LIBWOLFSSL_VERSION_STRING,
wolfSshIp, wolfSshPort, defaultFwdFromHost);
Expand Down Expand Up @@ -214,6 +217,120 @@ static int wsPublicKeyCheck(const byte* pubKey, word32 pubKeySz, void* ctx)
}


/* State shared with the remote-forward callbacks. A single reverse connection
* is supported here; a production tool would key a table by channel id. */
typedef struct PortfwdState {
const char* fwdToHost; /* local target to connect inbound channels to */
word16 fwdToPort;
SOCKET_T appFd; /* socket to the local target, -1 when idle */
word32 channelId; /* id of the inbound forwarded-tcpip channel */
int pending; /* a new channel is waiting to be wired up */
int replied; /* peer answered the tcpip-forward request */
int refused; /* ...and the answer was a refusal */
word16 boundPort; /* port the peer reported binding */
} PortfwdState;


/* Open a TCP connection to the local forward target. Returns -1 on failure. */
static SOCKET_T connectTarget(const char* host, word16 port)
{
SOCKADDR_IN_T addr;
SOCKET_T fd;

build_addr(&addr, host, port);
tcp_socket(&fd, ((struct sockaddr_in*)&addr)->sin_family);
if (connect(fd, (const struct sockaddr*)&addr, sizeof(addr)) != 0) {
WCLOSESOCKET(fd);
return (SOCKET_T)-1;
}
return fd;
}


/* Forwarding callback for client-side remote forwarding. The peer opens a
* forwarded-tcpip channel for each inbound connection on its listener; the
* library reports it here as a LOCAL_SETUP followed by a CHANNEL_ID. */
static int portfwdRemoteFwdCb(WS_FwdCbAction action, void* ctx,
const char* address, word32 port)
{
PortfwdState* st = (PortfwdState*)ctx;
int ret = WS_FWD_SUCCESS;

switch (action) {
case WOLFSSH_FWD_LOCAL_SETUP:
/* address/port describe the peer's bound listener, not the local
* target, so connect to the configured forward-to address. */
(void)address;
(void)port;
st->appFd = connectTarget(st->fwdToHost, st->fwdToPort);
if (st->appFd == (SOCKET_T)-1) {
printf("Couldn't connect to forward target %s:%u\n",
st->fwdToHost, st->fwdToPort);
ret = WS_FWD_SETUP_E;
}
break;
case WOLFSSH_FWD_CHANNEL_ID:
/* The new channel's id arrives in the port argument. */
st->channelId = port;
st->pending = 1;
break;
case WOLFSSH_FWD_LOCAL_CLEANUP:
(void)address;
(void)port;
if (st->appFd != (SOCKET_T)-1) {
WCLOSESOCKET(st->appFd);
st->appFd = (SOCKET_T)-1;
}
break;
case WOLFSSH_FWD_REMOTE_SETUP:
case WOLFSSH_FWD_REMOTE_CLEANUP:
/* Server-side actions; a client requesting remote forwarding does
* not receive these. */
default:
break;
}

return ret;
}


/* Request-success callback. The reply to a want-reply tcpip-forward carries the
* bound port (relevant when port 0 was requested). */
static int portfwdReqSuccessCb(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
{
PortfwdState* st = (PortfwdState*)ctx;
const byte* p = (const byte*)buf;

(void)ssh;
if (p != NULL && sz >= 4) {
word32 boundPort = ((word32)p[0] << 24) | ((word32)p[1] << 16) |
((word32)p[2] << 8) | (word32)p[3];
st->boundPort = (word16)boundPort;
printf("Remote forward established; peer bound port %u\n", boundPort);
}
else {
/* The port is only echoed back when 0 was requested. */
printf("Remote forward established.\n");
}
st->replied = 1;
return WS_SUCCESS;
}


/* Request-failure callback. The peer refused the remote listener. */
static int portfwdReqFailureCb(WOLFSSH* ssh, void* buf, word32 sz, void* ctx)
{
PortfwdState* st = (PortfwdState*)ctx;

(void)ssh;
(void)buf;
(void)sz;
st->replied = 1;
st->refused = 1;
return WS_SUCCESS;
}


/*
* fwdFromHost - address to bind the local listener socket to (default: any)
* fwdFromHostPort - port number to bind the local listener socket to
Expand Down Expand Up @@ -243,7 +360,7 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)
SOCKET_T sshFd;
SOCKADDR_IN_T fwdFromHostAddr;
socklen_t fwdFromHostAddrSz = sizeof(fwdFromHostAddr);
SOCKET_T listenFd;
SOCKET_T listenFd = -1;
SOCKET_T appFd = -1;
int argc = ((func_args*)args)->argc;
char** argv = ((func_args*)args)->argv;
Expand All @@ -254,6 +371,8 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)
int ret;
int ch;
int appFdSet = 0;
int reverse = 0;
PortfwdState fwdState;
struct timeval to;
WOLFSSH_CHANNEL* fwdChannel = NULL;
byte* appBuffer = NULL;
Expand All @@ -269,7 +388,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:R:T:")) != -1) {
while ((ch = mygetopt(argc, argv, "?rf:h:p:t:u:F:P:R:T:")) != -1) {
switch (ch) {
case 'h':
host = myoptarg;
Expand Down Expand Up @@ -317,6 +436,10 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)
fwdToHost = myoptarg;
break;

case 'r':
reverse = 1;
break;

case '?':
ShowUsage();
exit(EXIT_SUCCESS);
Expand Down Expand Up @@ -388,15 +511,34 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)
if (ret != WS_SUCCESS)
err_sys("Couldn't set the username.");

if (reverse) {
/* The peer (SSH server) does the listening; we receive its inbound
* forwarded-tcpip channels through the forwarding callback and the
* bound-port reply through the request-success callback. */
memset(&fwdState, 0, sizeof(fwdState));
fwdState.fwdToHost = fwdToHost;
fwdState.fwdToPort = fwdToPort;
fwdState.appFd = (SOCKET_T)-1;
wolfSSH_CTX_SetFwdCb(ctx, portfwdRemoteFwdCb, NULL);
wolfSSH_SetFwdCbCtx(ssh, &fwdState);
wolfSSH_SetReqSuccess(ctx, portfwdReqSuccessCb);
wolfSSH_SetReqSuccessCtx(ssh, &fwdState);
wolfSSH_SetReqFailure(ctx, portfwdReqFailureCb);
wolfSSH_SetReqFailureCtx(ssh, &fwdState);
}

/* Socket to SSH peer. */
build_addr(&hostAddr, host, port);
tcp_socket(&sshFd, ((struct sockaddr_in *)&hostAddr)->sin_family);

/* Receive from client application or connect to server application. */
build_addr(&fwdFromHostAddr, fwdFromHost, fwdFromPort);
tcp_socket(&listenFd, ((struct sockaddr_in *)&fwdFromHostAddr)->sin_family);
if (!reverse) {
/* Receive from client application or connect to server application. */
build_addr(&fwdFromHostAddr, fwdFromHost, fwdFromPort);
tcp_socket(&listenFd,
((struct sockaddr_in *)&fwdFromHostAddr)->sin_family);

tcp_listen(&listenFd, &fwdFromPort, 1);
tcp_listen(&listenFd, &fwdFromPort, 1);
}

printf("Connecting to the SSH server...\n");
ret = connect(sshFd, (const struct sockaddr *)&hostAddr, hostAddrSz);
Expand All @@ -411,15 +553,38 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)
if (ret != WS_SUCCESS)
err_sys("Couldn't connect SFTP");

if (reverse) {
/* Ask the server to open a listener and tunnel connections back. */
ret = wolfSSH_FwdRemoteSetup(ssh, fwdFromHost, fwdFromPort, 1);
if (ret != WS_SUCCESS)
err_sys("Couldn't request remote port forward.");

/* The listener only exists once the peer answers. Want-reply was
* set, so a success or failure is owed. */
while (!fwdState.replied) {
ret = wolfSSH_worker(ssh, NULL);
if (ret != WS_SUCCESS && ret != WS_CHAN_RXD && ret != WS_WANT_READ)
err_sys("Couldn't get the remote forward reply.");
}
if (fwdState.refused)
err_sys("Peer refused the remote port forward request.");
}

if (readyFile != NULL) {
#ifndef NO_FILESYSTEM
WFILE* f = NULL;
word16 readyPort = fwdFromPort;

/* With -f 0 the peer picks the port and reports it in the reply. */
if (reverse && fwdState.boundPort != 0)
readyPort = fwdState.boundPort;

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);
l = WSNPRINTF(portStr, sizeof(portStr), "%d\n", (int)readyPort);
if (l > 0) {
WFWRITE(NULL, portStr, MIN((size_t)l, sizeof(portStr)), 1, f);
}
Expand All @@ -432,8 +597,13 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)

FD_ZERO(&templateFds);
FD_SET(sshFd, &templateFds);
FD_SET(listenFd, &templateFds);
nFds = findMax(sshFd, listenFd) + 1;
if (!reverse) {
FD_SET(listenFd, &templateFds);
nFds = findMax(sshFd, listenFd) + 1;
}
else {
nFds = (int)sshFd + 1;
}

for (;;) {
rxFds = templateFds;
Expand All @@ -453,7 +623,7 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)

if ((appFdSet && FD_ISSET(appFd, &errFds)) ||
FD_ISSET(sshFd, &errFds) ||
FD_ISSET(listenFd, &errFds)) {
(!reverse && FD_ISSET(listenFd, &errFds))) {

err_sys("some socket had an error");
}
Expand Down Expand Up @@ -490,8 +660,22 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)
}
}
}

/* A reverse channel may have been opened by the peer while the
* worker ran. Wire its local target socket into the select set. */
if (reverse && fwdState.pending && !appFdSet) {
appFd = fwdState.appFd;
fwdChannel = wolfSSH_ChannelFind(ssh, fwdState.channelId,
WS_CHANNEL_ID_SELF);
if (appFd != (SOCKET_T)-1 && fwdChannel != NULL) {
FD_SET(appFd, &templateFds);
nFds = findMax((int)sshFd, (int)appFd) + 1;
appFdSet = 1;
}
fwdState.pending = 0;
}
}
if (!appFdSet && FD_ISSET(listenFd, &rxFds)) {
if (!reverse && !appFdSet && FD_ISSET(listenFd, &rxFds)) {
appFd = accept(listenFd,
(struct sockaddr*)&fwdFromHostAddr, &fwdFromHostAddrSz);
if (appFd < 0)
Expand All @@ -515,12 +699,16 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)
}
}

if (reverse)
wolfSSH_FwdRemoteCancel(ssh, fwdFromHost, fwdFromPort, 0);
Comment on lines +702 to +703

ret = wolfSSH_shutdown(ssh);
if (ret != WS_SUCCESS)
err_sys("Closing port forward stream failed.");

WCLOSESOCKET(sshFd);
WCLOSESOCKET(listenFd);
if (listenFd != (SOCKET_T)-1)
WCLOSESOCKET(listenFd);
WCLOSESOCKET(appFd);
wolfSSH_free(ssh);
wolfSSH_CTX_free(ctx);
Expand Down
Loading
Loading