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
176 changes: 164 additions & 12 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 @@ -212,6 +215,102 @@ 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 */
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 {
printf("Remote forward established.\n");
}
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 @@ -241,7 +340,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 @@ -252,6 +351,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 @@ -267,7 +368,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 @@ -315,6 +416,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 @@ -386,15 +491,32 @@ 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);
}

/* 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 @@ -409,6 +531,13 @@ 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.");
}

if (readyFile != NULL) {
#ifndef NO_FILESYSTEM
WFILE* f = NULL;
Expand All @@ -430,8 +559,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 @@ -451,7 +585,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 @@ -488,8 +622,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 @@ -513,12 +661,16 @@ THREAD_RETURN WOLFSSH_THREAD portfwd_worker(void* args)
}
}

if (reverse)
wolfSSH_FwdRemoteCancel(ssh, fwdFromHost, fwdFromPort, 0);

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
61 changes: 61 additions & 0 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -14409,6 +14409,67 @@ int SendGlobalRequest(WOLFSSH* ssh,
return ret;
}


#ifdef WOLFSSH_FWD
/* Send a "tcpip-forward" (or "cancel-tcpip-forward") global request asking the
* peer to set up (or tear down) a remote listener. The request-specific data
* follows the want-reply boolean, so the generic SendGlobalRequest() framing
* (which places the boolean last) cannot express it. See RFC 4254 7.1. */
int SendGlobalRequestFwd(WOLFSSH* ssh,
const char* bindAddr, word32 bindPort, int isCancel, int wantReply)
{
byte* output;
word32 idx = 0;
word32 reqNameSz;
word32 bindAddrSz;
const char* reqName;
int ret = WS_SUCCESS;

WLOG(WS_LOG_DEBUG, "Entering SendGlobalRequestFwd()");

if (ssh == NULL || bindAddr == NULL)
ret = WS_BAD_ARGUMENT;

if (ret == WS_SUCCESS) {
reqName = isCancel ? "cancel-tcpip-forward" : "tcpip-forward";
reqNameSz = (word32)WSTRLEN(reqName);
bindAddrSz = (word32)WSTRLEN(bindAddr);

ret = PreparePacket(ssh, MSG_ID_SZ + LENGTH_SZ + reqNameSz + BOOLEAN_SZ
+ LENGTH_SZ + bindAddrSz + UINT32_SZ);
}

if (ret == WS_SUCCESS) {
output = ssh->outputBuffer.buffer;
idx = ssh->outputBuffer.length;

output[idx++] = MSGID_GLOBAL_REQUEST;
c32toa(reqNameSz, output + idx);
idx += LENGTH_SZ;
WMEMCPY(output + idx, reqName, reqNameSz);
idx += reqNameSz;
output[idx++] = (byte)(wantReply != 0);
c32toa(bindAddrSz, output + idx);
idx += LENGTH_SZ;
WMEMCPY(output + idx, bindAddr, bindAddrSz);
idx += bindAddrSz;
c32toa(bindPort, output + idx);
idx += UINT32_SZ;

ssh->outputBuffer.length = idx;

ret = BundlePacket(ssh);
}

if (ret == WS_SUCCESS)
ret = wolfSSH_SendPacket(ssh);

WLOG(WS_LOG_DEBUG, "Leaving SendGlobalRequestFwd(), ret = %d", ret);

return ret;
}
#endif /* WOLFSSH_FWD */

static const char cannedLangTag[] = "en-us";
static const word32 cannedLangTagSz = (word32)sizeof(cannedLangTag) - 1;

Expand Down
33 changes: 33 additions & 0 deletions src/ssh.c
Original file line number Diff line number Diff line change
Expand Up @@ -2875,6 +2875,39 @@ WOLFSSH_CHANNEL* wolfSSH_ChannelFwdNew(WOLFSSH* ssh,
return wolfSSH_ChannelFwdNewLocal(ssh, host, hostPort, origin, originPort);
}


/* Ask the peer to open a remote listener (client-side remote port forwarding).
* Sends a "tcpip-forward" global request for bindAddr:bindPort. A bindPort of 0
* asks the peer to choose a port; with wantReply set the peer reports it back
* through the request-success callback (wolfSSH_SetReqSuccess). The peer then
* opens a "forwarded-tcpip" channel for each inbound connection, delivered
* through the forwarding callback (wolfSSH_CTX_SetFwdCb). */
int wolfSSH_FwdRemoteSetup(WOLFSSH* ssh, const char* bindAddr,
word32 bindPort, int wantReply)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_FwdRemoteSetup()");
if (ssh == NULL || bindAddr == NULL)
return WS_BAD_ARGUMENT;
if (wantReply != 0 && wantReply != 1)
return WS_BAD_ARGUMENT;
return SendGlobalRequestFwd(ssh, bindAddr, bindPort, 0, wantReply);
}


/* Tear down a remote listener previously set up with wolfSSH_FwdRemoteSetup().
* Sends a "cancel-tcpip-forward" global request for the same bindAddr:bindPort
* that was requested. */
int wolfSSH_FwdRemoteCancel(WOLFSSH* ssh, const char* bindAddr,
word32 bindPort, int wantReply)
{
WLOG(WS_LOG_DEBUG, "Entering wolfSSH_FwdRemoteCancel()");
if (ssh == NULL || bindAddr == NULL)
return WS_BAD_ARGUMENT;
if (wantReply != 0 && wantReply != 1)
return WS_BAD_ARGUMENT;
return SendGlobalRequestFwd(ssh, bindAddr, bindPort, 1, wantReply);
}

#endif /* WOLFSSH_FWD */


Expand Down
4 changes: 4 additions & 0 deletions wolfssh/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,10 @@ WOLFSSH_LOCAL int SendGlobalRequestFwdSuccess(WOLFSSH * ssh, int success,
word32 port);
WOLFSSH_LOCAL int SendGlobalRequest(WOLFSSH * ssh,
const unsigned char * data, word32 dataSz, int reply);
#ifdef WOLFSSH_FWD
WOLFSSH_LOCAL int SendGlobalRequestFwd(WOLFSSH* ssh,
const char* bindAddr, word32 bindPort, int isCancel, int wantReply);
#endif
WOLFSSH_LOCAL int SendDebug(WOLFSSH* ssh, byte alwaysDisplay, const char* msg);
WOLFSSH_LOCAL int SendServiceRequest(WOLFSSH* ssh, byte serviceId);
WOLFSSH_LOCAL int SendServiceAccept(WOLFSSH* ssh, byte serviceId);
Expand Down
Loading
Loading