Created
November 30, 2022 15:41
-
-
Save hardening/61bfb8bf0ad1266ed20123ee05f282ba to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* FreeRDP: A Remote Desktop Protocol Implementation | |
* SOCKS MarIO | |
* | |
* Copyright 2022 David Fort <contact@hardening-consulting.com> | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <winpr/string.h> | |
#include <winpr/wlog.h> | |
#include <freerdp/types.h> | |
#include <plumber/mario_internal.h> | |
#include <plumber/mario_socks.h> | |
#define TAG "proxy" | |
/* SOCKS Proxy auth methods by rfc1928 */ | |
enum | |
{ | |
AUTH_M_NO_AUTH = 0, | |
AUTH_M_GSSAPI = 1, | |
AUTH_M_USR_PASS = 2 | |
}; | |
enum | |
{ | |
SOCKS_CMD_CONNECT = 1, | |
SOCKS_CMD_BIND = 2, | |
SOCKS_CMD_UDP_ASSOCIATE = 3 | |
}; | |
enum | |
{ | |
SOCKS_ADDR_IPV4 = 1, | |
SOCKS_ADDR_FQDN = 3, | |
SOCKS_ADDR_IPV6 = 4, | |
}; | |
/** @brief connect context */ | |
typedef struct { | |
MarioConnectContext base; | |
int nextState; | |
int nauthMethods; | |
size_t hostnlen; | |
BYTE buf[3 + 255 + 255]; /* biggest packet is user/pass auth */ | |
BYTE checkVersionByte; | |
} SocksConnectContext; | |
/** @brief SOCKS MarIo */ | |
typedef struct { | |
MarIo base; | |
SocksConnectContext connectContext; | |
char *proxyUsername; | |
char *proxyPassword; | |
PlumberSocksAddressType targetAddrType; | |
char *targetHost; | |
UINT16 targetPort; | |
struct sockaddr_storage targetAddr; | |
size_t targetAddrLen; | |
BYTE boundAddressType; | |
struct sockaddr_storage boundAddress; | |
CHAR boundDomainName[256]; | |
UINT16 boundPort; | |
} MarIoSocks; | |
MarIoStatus statusFromSocksConnectError(BYTE res) { | |
switch(res) { | |
case 0: | |
return MARIO_SUCCESS; | |
case 3: /* Network unreachable */ | |
case 4: /* Host unreachable */ | |
return MARIO_NO_ROUTE; | |
case 5: | |
return MARIO_CONNECTION_REFUSED; | |
case 1: /* failure */ | |
case 2: /* connection not allowed by ruleset */ | |
case 6: /* TTL expired */ | |
case 7: /* command not supported */ | |
case 8: /* address type not supported */ | |
default: | |
return MARIO_INTERNAL_ERROR; | |
} | |
} | |
static MarioConnectContext* socks_prepare_connect(MarIo* io, MarIoAsyncResult* res, UINT32 timeout, MarIoOpCb cb, void* userdata) | |
{ | |
MarIoSocks* socks = (MarIoSocks*)io; | |
SocksConnectContext* connectContext = &socks->connectContext; | |
mario_prepare_connect_context(&connectContext->base, io, res, timeout, cb, userdata); | |
connectContext->checkVersionByte = 5; | |
connectContext->hostnlen = strnlen(socks->targetHost, 255); | |
connectContext->nextState = 0; | |
connectContext->nauthMethods = 1; | |
return &connectContext->base; | |
} | |
static BOOL socks_connect(MarIo* io, MarioConnectContext* bcontext); | |
static void connect_readwrite_cb(MarIo* io, MarIoAsyncResult* res, void* userdata) | |
{ | |
SocksConnectContext* context = (SocksConnectContext*)userdata; | |
MarioConnectContext* bcontext = &context->base; | |
if (socks_connect(bcontext->io, bcontext)) | |
bcontext->cb(bcontext->io, bcontext->res, bcontext->userdata); | |
} | |
static BOOL socks_connect(MarIo* io, MarioConnectContext* bcontext) | |
{ | |
MarIoAsyncResult* res = bcontext->res; | |
SocksConnectContext* context = (SocksConnectContext*)bcontext; | |
MarIoSocks* socks = (MarIoSocks*)io; | |
int writeLen; | |
int targetIndex; | |
#define LABEL(N) case N: goto label_##N | |
switch (context->nextState) { | |
LABEL(0); | |
LABEL(1); | |
LABEL(2); | |
LABEL(3); | |
LABEL(4); | |
LABEL(5); | |
LABEL(6); | |
LABEL(7); | |
LABEL(10); | |
LABEL(11); | |
LABEL(12); | |
default: | |
WLog_ERR(TAG, "%s: Internal error, unknown jump target", __FUNCTION__); | |
res->status = MARIO_INTERNAL_ERROR; | |
return TRUE; | |
} | |
/* First connect to the server */ | |
label_0: | |
context->nextState = 1; | |
if (!mario_connect(io->next, res, bcontext->timeout, connect_readwrite_cb, context)) | |
return FALSE; | |
label_1: | |
if (res->status != MARIO_SUCCESS) | |
return TRUE; | |
writeLen = 3; | |
if (socks->proxyUsername && socks->proxyPassword) | |
{ | |
context->nauthMethods++; | |
writeLen++; | |
} | |
/* select auth. method */ | |
context->buf[0] = 5; /* SOCKS version */ | |
context->buf[1] = context->nauthMethods; /* #of methods offered */ | |
context->buf[2] = AUTH_M_NO_AUTH; | |
if (context->nauthMethods > 1) | |
context->buf[3] = AUTH_M_USR_PASS; | |
context->nextState = 2; | |
if (!mario_write(io->next, res, context->buf, writeLen, connect_readwrite_cb, context)) | |
return FALSE; | |
label_2: | |
if (res->status != MARIO_SUCCESS) | |
return TRUE; | |
//status = recv_socks_reply(bufferedBio, buf, 2, "AUTH REQ", 5); | |
context->nextState = 3; | |
if (!mario_read(io->next, res, context->buf, 2, TRUE, connect_readwrite_cb, context)) | |
return FALSE; | |
label_3: | |
if (res->status != MARIO_SUCCESS) | |
return TRUE; | |
switch (context->buf[1]) | |
{ | |
case AUTH_M_NO_AUTH: | |
WLog_DBG(TAG, "SOCKS Proxy: (NO AUTH) method was selected"); | |
break; | |
case AUTH_M_USR_PASS: | |
if (!socks->proxyUsername || !socks->proxyPassword) { | |
res->status = MARIO_UNSUPPORTED_ITEM; | |
return TRUE; | |
} | |
int usernameLen = strnlen(socks->proxyUsername, 255); | |
int userpassLen = strnlen(socks->proxyPassword, 255); | |
BYTE* ptr; | |
if (context->nauthMethods < 2) | |
{ | |
WLog_ERR(TAG, "SOCKS Proxy: USER/PASS method was not proposed to server"); | |
res->status = MARIO_AUTH_ERROR; | |
return TRUE; | |
} | |
/* user/password v1 method */ | |
ptr = &context->buf[2]; | |
context->buf[0] = 1; | |
context->buf[1] = usernameLen; | |
memcpy(ptr, socks->proxyUsername, usernameLen); | |
ptr += usernameLen; | |
*ptr = userpassLen; | |
ptr++; | |
memcpy(ptr, socks->proxyPassword, userpassLen); | |
context->nextState = 4; | |
if (!mario_write(io->next, res, context->buf, 3 + usernameLen + userpassLen, connect_readwrite_cb, context)) | |
return FALSE; | |
label_4: | |
if (res->status != MARIO_SUCCESS) { | |
WLog_ERR(TAG, "SOCKS Proxy: error writing user/password request"); | |
return TRUE; | |
} | |
context->nextState = 5; | |
if (!mario_read(io->next, res, context->buf, 2, TRUE, connect_readwrite_cb, context)) | |
return FALSE; | |
label_5: | |
//status = recv_socks_reply(bufferedBio, buf, 2, "AUTH REQ", 1); | |
if (res->status != MARIO_SUCCESS) { | |
WLog_ERR(TAG, "SOCKS Proxy: error writing user/password request"); | |
return TRUE; | |
} | |
if (context->buf[1] != 0x00) | |
{ | |
WLog_ERR(TAG, "SOCKS Proxy: invalid user/password"); | |
res->status = MARIO_AUTH_ERROR; | |
return TRUE; | |
} | |
break; | |
default: | |
WLog_ERR(TAG, "SOCKS Proxy: unknown method 0x%x was selected by proxy", context->buf[1]); | |
res->status = MARIO_INTERNAL_ERROR; | |
return TRUE; | |
} | |
/* CONN request */ | |
context->buf[0] = 5; /* SOCKS version */ | |
context->buf[1] = SOCKS_CMD_CONNECT; /* command */ | |
context->buf[2] = 0; /* 3rd octet is reserved x00 */ | |
targetIndex = 4; | |
switch(socks->targetAddrType) | |
{ | |
case SOCKS_CONNECT_BY_DOMAIN: | |
context->buf[3] = SOCKS_ADDR_FQDN; /* addr.type */ | |
context->buf[4] = context->hostnlen; /* DST.ADDR */ | |
memcpy(&context->buf[5], socks->targetHost, context->hostnlen); | |
targetIndex += 1 + context->hostnlen; | |
break; | |
case SOCKS_CONNECT_BY_ADDR: | |
if (socks->targetAddr.ss_family == AF_INET) { | |
struct sockaddr_in* addr = (struct sockaddr_in*)&socks->targetAddr; | |
context->buf[3] = SOCKS_ADDR_IPV4; /* addr.type */ | |
memcpy(&context->buf[4], &addr->sin_addr, 4); | |
targetIndex += 4; | |
} else if (socks->targetAddr.ss_family == AF_INET6) { | |
struct sockaddr_in6* addr = (struct sockaddr_in6*)&socks->targetAddr; | |
context->buf[3] = SOCKS_ADDR_IPV6; /* addr.type */ | |
memcpy(&context->buf[4], &addr->sin6_addr, 16); | |
targetIndex += 16; | |
} | |
break; | |
default: | |
res->status = MARIO_UNSUPPORTED_ITEM; | |
return TRUE; | |
} | |
/* follows DST.PORT in netw. format */ | |
context->buf[targetIndex++] = (socks->targetPort >> 8) & 0xff; | |
context->buf[targetIndex++] = socks->targetPort & 0xff; | |
context->nextState = 6; | |
if (!mario_write(io->next, res, context->buf, targetIndex, connect_readwrite_cb, context)) | |
return FALSE; | |
label_6: | |
if (res->status != MARIO_SUCCESS) | |
{ | |
WLog_ERR(TAG, "SOCKS proxy: failed to write CONN REQ"); | |
return TRUE; | |
} | |
context->nextState = 7; | |
//status = recv_socks_reply(bufferedBio, buf, sizeof(buf), "CONN REQ", 5); | |
if (!mario_read(io->next, res, context->buf, 4, TRUE, connect_readwrite_cb, context)) | |
return FALSE; | |
label_7: | |
if (res->status != MARIO_SUCCESS) | |
{ | |
WLog_ERR(TAG, "SOCKS proxy: unable to read CONN REQ"); | |
return TRUE; | |
} | |
res->status = statusFromSocksConnectError(context->buf[1]); | |
if (res->status != MARIO_SUCCESS) | |
{ | |
WLog_INFO(TAG, "SOCKS connection failed"); | |
return TRUE; | |
} | |
int toRead; | |
BYTE* readTarget; | |
struct sockaddr_in* addr_in = (struct sockaddr_in*)&socks->boundAddress; | |
struct sockaddr_in6* addr_in6 = (struct sockaddr_in6*)&socks->boundAddress; | |
socks->boundAddressType = context->buf[3]; | |
switch (socks->boundAddressType) { | |
case SOCKS_ADDR_IPV4: | |
/* IPv4 */ | |
socks->boundAddress.ss_family = AF_INET; | |
readTarget = (BYTE*)&addr_in->sin_addr; | |
toRead = 4; | |
break; | |
case SOCKS_ADDR_FQDN: | |
/* domainName */ | |
readTarget = context->buf; | |
toRead = 1; | |
break; | |
case SOCKS_ADDR_IPV6: | |
/* IPv6 */ | |
socks->boundAddress.ss_family = AF_INET6; | |
readTarget = (BYTE*)&addr_in6->sin6_addr; | |
toRead = 16; | |
break; | |
default: | |
res->status = MARIO_UNSUPPORTED_ITEM; | |
return TRUE; | |
} | |
context->nextState = 10; | |
if (!mario_read(io->next, res, readTarget, toRead, TRUE, connect_readwrite_cb, context)) | |
return FALSE; | |
label_10: | |
if (res->status != MARIO_SUCCESS) | |
{ | |
WLog_INFO(TAG, "error retrieving bound address"); | |
return TRUE; | |
} | |
if (socks->boundAddressType == SOCKS_ADDR_FQDN) { | |
/* read the content of the domainName */ | |
context->nextState = 11; | |
toRead = context->buf[0]; | |
socks->boundDomainName[toRead] = '\00'; | |
if (!mario_read(io->next, res, (BYTE *)socks->boundDomainName, toRead, TRUE, connect_readwrite_cb, context)) | |
return FALSE; | |
label_11: | |
if (res->status != MARIO_SUCCESS) | |
{ | |
WLog_INFO(TAG, "unable to read domainName"); | |
return TRUE; | |
} | |
socks->boundDomainName[res->treatedBytes] = '\x00'; | |
} | |
/* Read BND.PORT */ | |
context->nextState = 12; | |
if (!mario_read(io->next, res, (BYTE *)&socks->boundPort, 2, TRUE, connect_readwrite_cb, context)) | |
return FALSE; | |
label_12: | |
if (res->status != MARIO_SUCCESS) | |
{ | |
WLog_INFO(TAG, "unable to read BND.PORT"); | |
return TRUE; | |
} | |
return TRUE; | |
} | |
MarIo* plumber_socks_client_new(MarIo* next, PlumberSocksVersion version, const char* username, const char* password) | |
{ | |
MarIoSocks* ret = (MarIoSocks*)mario_base_new(sizeof(*ret), MARIO_KIND_SOCKS, FALSE); | |
if (!ret) | |
return NULL; | |
if (username && password) | |
{ | |
ret->proxyUsername = _strdup(username); | |
if (!ret->proxyUsername) | |
goto error_username; | |
ret->proxyPassword = _strdup(password); | |
if (!ret->proxyPassword) | |
goto error_password; | |
} | |
ret->targetAddrType = SOCKS_CONNECT_NOT_SET; | |
MarIo* base = &ret->base; | |
base->next = next; | |
base->prepareConnect = socks_prepare_connect; | |
base->connect = socks_connect; | |
base->prepareRead = mario_standard_prepare_read_next; | |
base->read = mario_standard_read_next; | |
base->prepareWrite = mario_standard_prepare_write_next; | |
base->write = mario_standard_write_next; | |
return base; | |
error_password: | |
free(ret->proxyUsername); | |
error_username: | |
free(ret); | |
return NULL; | |
} | |
BOOL plumber_socks_client_set_by_addr(MarIo* io, const struct sockaddr* addr, size_t len) | |
{ | |
ASSERT_VALID_MARIO(io); | |
MarIoSocks* socks = (MarIoSocks*)io; | |
if (io->kind != MARIO_KIND_SOCKS) | |
{ | |
WLog_ERR(TAG, "io is not a SOCKS MarIo"); | |
return FALSE; | |
} | |
socks->targetAddrType = SOCKS_CONNECT_BY_ADDR; | |
socks->targetAddrLen = MIN(sizeof(socks->targetAddr), len); | |
memcpy(&socks->targetAddr, addr, socks->targetAddrLen); | |
struct sockaddr_in* addr_in; | |
struct sockaddr_in6* addr_in6; | |
switch(addr->sa_family) { | |
case AF_INET: | |
addr_in = (struct sockaddr_in*)addr; | |
socks->targetPort = htons(addr_in->sin_port); | |
break; | |
case AF_INET6: | |
addr_in6 = (struct sockaddr_in6*)addr; | |
socks->targetPort = htons(addr_in6->sin6_port); | |
break; | |
} | |
return TRUE; | |
} | |
BOOL plumber_socks_client_set_by_name(MarIo* io, const char* target, UINT16 port) | |
{ | |
ASSERT_VALID_MARIO(io); | |
MarIoSocks* socks = (MarIoSocks*)io; | |
if (io->kind != MARIO_KIND_SOCKS) | |
{ | |
WLog_ERR(TAG, "io is not a SOCKS MarIo"); | |
return FALSE; | |
} | |
socks->targetAddrType = SOCKS_CONNECT_BY_DOMAIN; | |
socks->targetHost = _strdup(target); | |
if (!socks->targetHost) | |
return FALSE; | |
socks->targetPort = port; | |
return TRUE; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment