/*****************************************************************************/ /* ProxySocks.c RFC 1928 SOCKS Version 5 proxy for TCP/IP. https://tools.ietf.org/html/rfc1928 This module supports only CONNECT TCP/IP and not BIND or UDP-associate. This is not a "SOCKS5 compliant" implementation since the RFC requires that compliant implementation MUST support GSSAPI, which this does not. Username/Password Authentication for SOCKS V5 https://tools.ietf.org/html/rfc1929 VERSION HISTORY --------------- 15-MAR-2021 MGD initial */ /*****************************************************************************/ #ifdef WASD_VMS_V7 #undef _VMS__V6__SOURCE #define _VMS__V6__SOURCE #undef __VMS_VER #define __VMS_VER 70000000 #undef __CRTL_VER #define __CRTL_VER 70000000 #endif /* standard C header files */ #include #include /* VMS related header files */ #include #include #include #include /* application related header files */ #include "wasd.h" #define WASD_MODULE "PROXYSOCKS" static char *Socks5Atyp [] = { "*error*", "IPv4", "*error*", "domain", "IPv6" }; static char *Socks5Cmd [] = { "*error*", "connect", "bind", "udp"}; static char *Socks5Reply [] = { "success", "failure", "ruleset", "netunreach", "hostunreach", "refused", "TTLexp", "command", "address" }; /******************/ /* global storage */ /******************/ /********************/ /* external storage */ /********************/ extern int ProxyReadBufferSize; extern int ToLowerCase[], ToUpperCase[]; extern char SoftwareID[]; extern PROXY_ACCOUNTING_STRUCT *ProxyAccountingPtr; extern WATCH_STRUCT Watch; /****************************************************************************/ /* Support SOCKS 5 proxy. RFC 1928 Section 3. */ void ProxySocks5 (REQUEST_STRUCT *rqptr) { static char ReplyAuthNotOk [] = { SOCKS5_VERSION, SOCKS5_AUTH_NOTOK }; static char ReplyAuthOk [] = { SOCKS5_VERSION, 0 }; char cnt, idx; char *bptr; /*********/ /* begin */ /*********/ if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "SOCKS5 authenticate !UL", rqptr->rqNet.ReadBufferPtr[1]); InstanceGblSecIncrLong (&ProxyAccountingPtr->Socks5Count); cnt = rqptr->rqNet.ReadBufferPtr[1]; bptr = rqptr->rqNet.ReadBufferPtr+2; for (idx = 0; idx < cnt; idx++) { if (rqptr->ServicePtr->ProxyAuthRequired) { if (bptr[idx] == SOCKS5_AUTH_USERPASS) break; } else if (bptr[idx] == SOCKS5_AUTH_NONE) break; } if (idx >= cnt) { /* no supported authentication method */ NetWrite (rqptr, &RequestEnd5, ReplyAuthNotOk, sizeof(ReplyAuthNotOk)); return; } ReplyAuthOk[1] = bptr[idx]; /* reply to the client that authorisation method is supported */ if (bptr[idx] == SOCKS5_AUTH_USERPASS) { if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "SOCKS5 authenticate USERPASS"); NetWrite (rqptr, 0, ReplyAuthOk, sizeof(ReplyAuthOk)); NetRead (rqptr, &ProxySocks5BasicAst, rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize); } else NetWrite (rqptr, &ProxySocks5GetRequest, ReplyAuthOk, sizeof(ReplyAuthOk)); } /****************************************************************************/ /* Process the SOCKS 5 basic authentication from the client. RFC 1929 section 2. +----+------+----------+------+----------+ |VER | ULEN | UNAME | PLEN | PASSWD | +----+------+----------+------+----------+ | 1 | 1 | 1 to 255 | 1 | 1 to 255 | +----+------+----------+------+----------+ WASD implements this in a slightly "cunning" fashion (to accomodate the way WASD proxy authentication operates in general). When the SOCKS client provides the username and password this is consolidated as a BASIC authentication base-64 encoded string *temporarily* in rqptr->RemoteUser + rqptr->RemoteUserPassword storage (not intended purpose), then incorporated into a Proxy-Authorization: header field when the request is constructed by ProxySocks5RequestAst(). So, WASD SOCK5 proxy authentication always returns to the client an initial authentication OK reply and accepts the client's request, from which it builds the proxy request which includes a Proxy-Authorization: header field with the previously supplied BASIC authentication. The proxy request is then processed and if not authorised *then* returns a failure reply. Not optimal but it works. The reason for the failure (not authorised) can be WATCHed for. */ void ProxySocks5BasicAst (REQUEST_STRUCT *rqptr) { static char ReplyAuthNotOk [] = { SOCKS5_VERSION, SOCKS5_AUTH_NOTOK }; static char ReplyAuthOk [] = { SOCKS5_VERSION, SOCKS5_AUTH_OK }; int cnt, idx, plen, ulen, b64len; char *aptr, *bptr, *bzptr, *sptr, *zptr; char UserPass [256]; /*********/ /* begin */ /*********/ if (VMSnok (rqptr->NetIoPtr->ReadStatus)) { /* read from client failed */ RequestEnd5 (rqptr); return; } cnt = rqptr->NetIoPtr->ReadCount; bptr = rqptr->rqNet.ReadBufferPtr; bzptr = bptr + cnt; if (*bptr++ != SOCKS5_AUTH_BASIC_VERSION) { /* questionable data */ ProxySocks5ReplyFail (rqptr); RequestEnd5 (rqptr); return; } /* second this storage that will not be used 'til after authorization */ zptr = (sptr = UserPass) + sizeof(UserPass)-1; for (cnt = *bptr++; cnt-- && bptr < bzptr && sptr < zptr; *sptr++ = *bptr++); if (cnt >= 0) { /* questionable data */ ProxySocks5ReplyFail (rqptr); RequestEnd5 (rqptr); return; } ulen = sptr - UserPass; if (sptr < zptr) *sptr++ = ':'; aptr = sptr; for (cnt = *bptr++; cnt-- && bptr < bzptr && sptr < zptr; *sptr++ = *bptr++); if (cnt >= 0) { /* questionable data */ ProxySocks5ReplyFail (rqptr); RequestEnd5 (rqptr); return; } plen = sptr - aptr; *sptr = '\0'; if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "SOCKS5 basic !#AZ:!#**", ulen, UserPass, plen); b64len = sizeof(rqptr->RemoteUser) + sizeof(rqptr->RemoteUserPassword); base64_encode (rqptr->RemoteUser, &b64len, UserPass, ulen+1+plen); NetWrite (rqptr, &ProxySocks5GetRequest, ReplyAuthOk, sizeof(ReplyAuthOk)); } /****************************************************************************/ /* Read the SOCKS 5 request from the client. RFC 1928 Section 4. */ void ProxySocks5GetRequest (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ if (VMSnok (rqptr->NetIoPtr->WriteStatus)) { /* write to client failed */ RequestEnd5 (rqptr); return; } /* read request from client */ NetRead (rqptr, &ProxySocks5RequestAst, rqptr->rqNet.ReadBufferPtr, rqptr->rqNet.ReadBufferSize); } /****************************************************************************/ /* Client has provided a command. RFC 1928 Section 4. Process that and if accepted generated a "faked" WASD proxy CONNECT request. +----+-----+-------+------+----------+----------+ |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ o VER protocol version: X'05' o CMD o CONNECT X'01' o BIND X'02' o UDP ASSOCIATE X'03' o RSV RESERVED o ATYP address type of following address o IP V4 address: X'01' o DOMAINNAME: X'03' o IP V6 address: X'04' o DST.ADDR desired destination address o DST.PORT desired destination port in network octet order */ void ProxySocks5RequestAst (REQUEST_STRUCT *rqptr) { #define STRCAT(string) { \ for (cptr = string; *cptr && sptr < zptr; *sptr++ = *cptr++); \ } int atyp, cmd, cnt, idx, len; ulong HostOrder [4]; ushort port; char *aptr, *bptr, *cptr, *sptr, *zptr; char dname [128], sport [8]; /*********/ /* begin */ /*********/ if (VMSnok (rqptr->NetIoPtr->ReadStatus)) { /* read from client failed */ RequestEnd5 (rqptr); return; } cnt = rqptr->NetIoPtr->ReadCount; bptr = rqptr->rqNet.ReadBufferPtr; if (cnt < 10 || bptr[0] != SOCKS5_VERSION) { /* questionable data */ ProxySocks5ReplyFail (rqptr); RequestEnd5 (rqptr); return; } cmd = bptr[1]; if (cmd < 1 || cmd > 3) cmd = 0; atyp = bptr[3]; if (atyp == SOCKS5_ATYPE_IPV4) { HostOrder[0] = *(ULONGPTR)(bptr+4); port = ntohs(*(USHORTPTR)(bptr+8)); sprintf (sport, ":%d", port); aptr = TcpIpAddressToString(HostOrder[0],4); if (cnt != 10) len = 0; } else if (atyp == SOCKS5_ATYPE_DOMAIN) { len = bptr[4]; aptr = dname; zptr = (sptr = dname) + sizeof(dname)-1; for (cptr = bptr + 5; len && sptr < zptr; len--) *sptr++ = *cptr++; *sptr = '\0'; port = ntohs(*(USHORTPTR)cptr); sprintf (sport, ":%d", port); len = bptr[4] + 7; if (len != cnt) len = 0; } else if (atyp == SOCKS5_ATYPE_IPV6) { HostOrder[0] = *(ULONGPTR)(bptr+4); HostOrder[1] = *(ULONGPTR)(bptr+8); HostOrder[2] = *(ULONGPTR)(bptr+12); HostOrder[3] = *(ULONGPTR)(bptr+16); port = ntohs(*(USHORTPTR)(bptr+20)); sprintf (sport, ":%d", port); aptr = TcpIpAddressToString(HostOrder,6); /* convert ':' to '-' delimited so not to confuse port */ for (cptr = aptr; *cptr; cptr++) if (*cptr == ':') *cptr = '-'; if (cnt != 22) len = 0; } else aptr = len = port = 0; if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "SOCKS5 !AZ !AZ !AZ port !UL", Socks5Cmd[cmd], Socks5Atyp[atyp], aptr, port); /* only SOCKS5 connect requests are supported */ if (!len || cmd != SOCKS5_CMD_CONNECT) { ProxySocks5ReplyFail (rqptr); RequestEnd5 (rqptr); return; } /* fake a CONNECT request */ zptr = (sptr = rqptr->rqNet.ReadBufferPtr) + rqptr->rqNet.ReadBufferSize; STRCAT ("CONNECT socks5://"); STRCAT (aptr); STRCAT (sport); STRCAT (" HTTP/1.1\r\nUser-Agent: "); STRCAT (SoftwareID); STRCAT ("\r\nHost: "); STRCAT (aptr); STRCAT (sport); if (rqptr->RemoteUser[0]) { STRCAT ("\r\nProxy-Authorization: Basic "); STRCAT (rqptr->RemoteUser); rqptr->RemoteUser[0] = rqptr->RemoteUserPassword[0] = '\0'; } STRCAT ("\r\nX-Forwarded-For: "); STRCAT (rqptr->ClientPtr->IpAddressString); STRCAT ("\r\nUpgrade: WASD-SOCKS5-"); STRCAT (Socks5Cmd[cmd]); STRCAT ("-"); STRCAT (Socks5Atyp[atyp]); STRCAT ("\r\n\r\n"); /* as if it came from the network */ rqptr->NetIoPtr->ReadCount = sptr - rqptr->rqNet.ReadBufferPtr; /* after this call the request will have a proxy task */ SysDclAst (RequestGet, rqptr); #undef STRCAT } /****************************************************************************/ /* Provide the client with succeeded/failed reply. RFC 1928 Section 6. All replies to the client are blocking and synchronous. +----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ Where: o VER protocol version: X'05' o REP Reply field: o X'00' succeeded o X'01' general SOCKS server failure o X'02' connection not allowed by ruleset o X'03' Network unreachable o X'04' Host unreachable o X'05' Connection refused o X'06' TTL expired o X'07' Command not supported o X'08' Address type not supported o X'09' to X'FF' unassigned o RSV RESERVED o ATYP address type of following address */ void ProxySocks5Reply ( REQUEST_STRUCT *rqptr, int ReplyField ) { int atyp, b_len, port, reply; uint blen; ulong Ip4Address; uchar Ip6Address; char *aptr, *bptr; PROXY_TASK *tkptr; /*********/ /* begin */ /*********/ tkptr = rqptr->ProxyTaskPtr; if (ReplyField < 0 || ReplyField > SOCKS5_REPLY_ADDRESS) { ProxySocks5ReplyFail (rqptr); ProxySocks5End (rqptr); return; } bptr = tkptr->Socks5Reply; bptr[0] = SOCKS5_VERSION; bptr[1] = reply = ReplyField; bptr[2] = SOCKS5_RESERVED; if (IPADDRESS_IS_V4 (&tkptr->ConnectIpAddress)) { bptr[3] = atyp = SOCKS5_ATYPE_IPV4; bptr += 4; IPADDRESS_SET4 (&Ip4Address, &tkptr->ConnectIpAddress) *(ULONGPTR)bptr = Ip4Address; aptr = TcpIpAddressToString(bptr,4); bptr += sizeof(Ip4Address); } else if (IPADDRESS_IS_V6 (&tkptr->ConnectIpAddress)) { bptr[3] = atyp = SOCKS5_ATYPE_IPV6; bptr += 4; memset (&Ip6Address, 0, sizeof(Ip6Address)); IPADDRESS_SET6 (Ip6Address, &tkptr->ConnectIpAddress) memcpy (bptr, &Ip6Address, sizeof(Ip6Address)); aptr = TcpIpAddressToString(bptr,6); bptr += sizeof(Ip6Address); } else { ProxySocks5ReplyFail (rqptr); ProxySocks5End (rqptr); return; } *(USHORTPTR)bptr = port = (ushort)tkptr->ConnectPort; bptr += 2; if (!ReplyField) InstanceGblSecIncrLong (&ProxyAccountingPtr->Socks5SuccessCount); else InstanceGblSecIncrLong (&ProxyAccountingPtr->Socks5FailCount); blen = bptr - tkptr->Socks5Reply; bptr = tkptr->Socks5Reply; if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "SOCKS5 !AZ !AZ !AZ port !UL", Socks5Reply[reply], Socks5Atyp[atyp], aptr, port); /* blocking with no AST delivery */ NetWrite (rqptr, NULL, bptr, blen); } /****************************************************************************/ /* All replies to the client are blocking and synchronous. */ void ProxySocks5ReplyFail (REQUEST_STRUCT *rqptr) { static char ReplyFail [] = { SOCKS5_VERSION, SOCKS5_REPLY_FAILURE, SOCKS5_RESERVED, SOCKS5_ATYPE_IPV4, 0, 0, 0, 0, 0, 0 }; /*********/ /* begin */ /*********/ if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "SOCKS5 reply FAIL"); InstanceGblSecIncrLong (&ProxyAccountingPtr->Socks5FailCount); NetWrite (rqptr, 0, ReplyFail, sizeof(ReplyFail)); } /****************************************************************************/ /* Call proxy end from an AST delivering a request pointer. */ void ProxySocks5End (REQUEST_STRUCT *rqptr) { /*********/ /* begin */ /*********/ ProxyEnd (rqptr->ProxyTaskPtr); } /****************************************************************************/ /* Don't support SOCKS 4 proxy. */ void ProxySocks4 (REQUEST_STRUCT *rqptr) { static char ReplyNotSup [] = { 4, 91, 0, 0, 0, 0, 0, 0, 0 }; /*********/ /* begin */ /*********/ if (WATCHING (rqptr, WATCH_PROXY)) WatchThis (WATCHITM(rqptr), WATCH_PROXY, "SOCKS4 unsupported"); NetWrite (rqptr, &RequestEnd5, ReplyNotSup, sizeof(ReplyNotSup)); } /*****************************************************************************/