upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/proxy.cpp
diff options
context:
space:
mode:
authorbitflash <admin@bitflash.network>2026-08-12 10:31:29 -0300
committerbitflash <admin@bitflash.network>2026-08-12 10:31:29 -0300
commitc8d798807e69ddbe55bfab8a34286ab75b786cb5 (patch)
tree81b5d44e5193f220b64097f496e314e04caf2db8 /src/proxy.cpp
parentc5f6b52c0d1d65f7bf8cd745a7a212bc6b2fe318 (diff)
parentd0ebe648f333def99fababd65f6b929448616d56 (diff)
test merge
# Conflicts: # src/makefile.mingw
Diffstat (limited to 'src/proxy.cpp')
-rw-r--r--src/proxy.cpp377
1 files changed, 377 insertions, 0 deletions
diff --git a/src/proxy.cpp b/src/proxy.cpp
new file mode 100644
index 0000000..7d30911
--- /dev/null
+++ b/src/proxy.cpp
@@ -0,0 +1,377 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3
4#include "headers_core.h"
5#include "proxy.h"
6#include "sockcount.h"
7
8#include <errno.h>
9
10static CCriticalSection cs_socks5;
11static bool g_fSocks5Proxy = false;
12static bool g_fTorProxy = false;
13static std::string g_socks5Host;
14static unsigned short g_socks5Port = 0;
15static const char* DEFAULT_TOR_SOCKS5_PROXY = "127.0.0.1:9050";
16
17static void SetSocketTimeout(SOCKET s, int nTimeoutSecs)
18{
19 if (nTimeoutSecs <= 0)
20 return;
21#ifdef _WIN32
22 DWORD tv = (DWORD)nTimeoutSecs * 1000;
23 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
24 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
25#else
26 struct timeval tv;
27 tv.tv_sec = nTimeoutSecs;
28 tv.tv_usec = 0;
29 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
30 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
31#endif
32}
33
34static bool ReadN(SOCKET s, void* buf, int n)
35{
36 char* p = (char*)buf;
37 int off = 0;
38 while (off < n)
39 {
40 int r = recv(s, p + off, n - off, 0);
41 if (r <= 0)
42 return false;
43 off += r;
44 }
45 return true;
46}
47
48static bool WriteN(SOCKET s, const void* buf, int n)
49{
50 const char* p = (const char*)buf;
51 int off = 0;
52 while (off < n)
53 {
54 int r = send(s, p + off, n - off, 0);
55 if (r <= 0)
56 return false;
57 off += r;
58 }
59 return true;
60}
61
62static bool ValidProxyHostChar(char c)
63{
64 return isalnum((unsigned char)c) || c == '.' || c == '-' || c == '_';
65}
66
67static bool ValidProxyIpv6HostChar(char c)
68{
69 return isxdigit((unsigned char)c) || c == ':' || c == '.';
70}
71
72static std::string LowerProxyHost(std::string host)
73{
74 if (!host.empty() && host[host.size() - 1] == '.')
75 host.erase(host.size() - 1);
76 for (size_t i = 0; i < host.size(); i++)
77 host[i] = (char)tolower((unsigned char)host[i]);
78 return host;
79}
80
81bool BtfIsTorOnionHost(const std::string& host)
82{
83 std::string lower = LowerProxyHost(host);
84 static const char* suffix = ".onion";
85 size_t suffixLen = strlen(suffix);
86 return lower.size() > suffixLen &&
87 lower.compare(lower.size() - suffixLen, suffixLen, suffix) == 0;
88}
89
90static bool ParsePort(const std::string& port, unsigned short& portOut,
91 std::string& errOut)
92{
93 if (port.empty())
94 {
95 errOut = "port is empty";
96 return false;
97 }
98 for (size_t i = 0; i < port.size(); i++)
99 {
100 if (!isdigit((unsigned char)port[i]))
101 {
102 errOut = "port is not numeric";
103 return false;
104 }
105 }
106
107 errno = 0;
108 char* end = NULL;
109 long nPort = strtol(port.c_str(), &end, 10);
110 if (errno == ERANGE || end == NULL || *end != '\0' || nPort <= 0 || nPort > 65535)
111 {
112 errOut = "port is out of range";
113 return false;
114 }
115 portOut = (unsigned short)nPort;
116 return true;
117}
118
119bool BtfParseSocks5Proxy(const std::string& spec, std::string& hostOut,
120 unsigned short& portOut, std::string& errOut)
121{
122 hostOut.clear();
123 portOut = 0;
124 errOut.clear();
125
126 std::string host;
127 std::string port;
128 bool fBracketedIpv6 = false;
129 if (!spec.empty() && spec[0] == '[')
130 {
131 size_t close = spec.find(']');
132 if (close == std::string::npos || close == 1 || close + 1 >= spec.size() || spec[close + 1] != ':')
133 {
134 errOut = "expected [IPv6]:PORT";
135 return false;
136 }
137 host = spec.substr(1, close - 1);
138 port = spec.substr(close + 2);
139 fBracketedIpv6 = true;
140 }
141 else
142 {
143 size_t colon = spec.rfind(':');
144 if (colon == std::string::npos || colon == 0 || colon + 1 >= spec.size())
145 {
146 errOut = "expected HOST:PORT";
147 return false;
148 }
149 host = spec.substr(0, colon);
150 port = spec.substr(colon + 1);
151 if (host.find(':') != std::string::npos)
152 {
153 errOut = "IPv6 proxy hosts must use [ADDR]:PORT";
154 return false;
155 }
156 }
157
158 if (host.size() > 255)
159 {
160 errOut = "host is too long";
161 return false;
162 }
163 if (fBracketedIpv6)
164 {
165 bool fHasColon = false;
166 for (size_t i = 0; i < host.size(); i++)
167 {
168 fHasColon = fHasColon || host[i] == ':';
169 if (!ValidProxyIpv6HostChar(host[i]))
170 {
171 errOut = "IPv6 proxy host contains unsupported characters";
172 return false;
173 }
174 }
175 if (!fHasColon)
176 {
177 errOut = "bracketed proxy host is not IPv6";
178 return false;
179 }
180 }
181 else
182 {
183 for (size_t i = 0; i < host.size(); i++)
184 {
185 if (!ValidProxyHostChar(host[i]))
186 {
187 errOut = "host contains unsupported characters";
188 return false;
189 }
190 }
191 }
192 if (!ParsePort(port, portOut, errOut))
193 return false;
194
195 hostOut = host;
196 return true;
197}
198
199static bool SetSocks5Proxy(const std::string& spec, bool fTorMode,
200 std::string& errOut)
201{
202 std::string host;
203 unsigned short port = 0;
204 if (!BtfParseSocks5Proxy(spec, host, port, errOut))
205 return false;
206 CRITICAL_BLOCK(cs_socks5)
207 {
208 g_socks5Host = host;
209 g_socks5Port = port;
210 g_fSocks5Proxy = true;
211 g_fTorProxy = fTorMode;
212 }
213 return true;
214}
215
216bool BtfSetSocks5Proxy(const std::string& spec, std::string& errOut)
217{
218 return SetSocks5Proxy(spec, false, errOut);
219}
220
221bool BtfEnableTorProxy(const std::string& spec, std::string& errOut)
222{
223 return SetSocks5Proxy(spec.empty() ? DEFAULT_TOR_SOCKS5_PROXY : spec, true, errOut);
224}
225
226void BtfClearSocks5Proxy()
227{
228 CRITICAL_BLOCK(cs_socks5)
229 {
230 g_fSocks5Proxy = false;
231 g_fTorProxy = false;
232 g_socks5Host.clear();
233 g_socks5Port = 0;
234 }
235}
236
237bool BtfSocks5ProxyEnabled()
238{
239 CRITICAL_BLOCK(cs_socks5)
240 return g_fSocks5Proxy;
241 return false;
242}
243
244bool BtfTorProxyEnabled()
245{
246 CRITICAL_BLOCK(cs_socks5)
247 return g_fSocks5Proxy && g_fTorProxy;
248 return false;
249}
250
251std::string BtfSocks5ProxyName()
252{
253 CRITICAL_BLOCK(cs_socks5)
254 {
255 if (!g_fSocks5Proxy)
256 return "";
257 if (g_socks5Host.find(':') != std::string::npos)
258 return strprintf("[%s]:%u", g_socks5Host.c_str(), (unsigned)g_socks5Port);
259 return strprintf("%s:%u", g_socks5Host.c_str(), (unsigned)g_socks5Port);
260 }
261 return "";
262}
263
264static SOCKET ConnectDirect(const std::string& host, unsigned short port,
265 int nSockSite, int nTimeoutSecs)
266{
267 struct addrinfo hints, *res = NULL, *rp = NULL;
268 memset(&hints, 0, sizeof(hints));
269 hints.ai_family = AF_UNSPEC;
270 hints.ai_socktype = SOCK_STREAM;
271 char portstr[16];
272 sprintf(portstr, "%u", (unsigned)port);
273 if (getaddrinfo(host.c_str(), portstr, &hints, &res) != 0 || !res)
274 return INVALID_SOCKET;
275
276 SOCKET s = INVALID_SOCKET;
277 for (rp = res; rp != NULL; rp = rp->ai_next)
278 {
279 s = BtfSocketTag(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol), nSockSite);
280 if (s == INVALID_SOCKET)
281 continue;
282 SetSocketTimeout(s, nTimeoutSecs);
283 if (connect(s, rp->ai_addr, (int)rp->ai_addrlen) == 0)
284 break;
285 BtfCloseSocket(s);
286 s = INVALID_SOCKET;
287 }
288 freeaddrinfo(res);
289 return s;
290}
291
292static bool Socks5Connect(SOCKET s, const std::string& destHost,
293 unsigned short destPort)
294{
295 if (destHost.empty() || destHost.size() > 255)
296 return false;
297
298 unsigned char hello[3] = { 0x05, 0x01, 0x00 }; // SOCKS5, one method, no auth
299 if (!WriteN(s, hello, sizeof(hello)))
300 return false;
301 unsigned char choice[2] = { 0, 0 };
302 if (!ReadN(s, choice, sizeof(choice)) || choice[0] != 0x05 || choice[1] != 0x00)
303 return false;
304
305 std::vector<unsigned char> req;
306 req.reserve(7 + destHost.size());
307 req.push_back(0x05); // version
308 req.push_back(0x01); // CONNECT
309 req.push_back(0x00); // reserved
310 req.push_back(0x03); // domain name; proxy resolves, avoiding local DNS leak
311 req.push_back((unsigned char)destHost.size());
312 req.insert(req.end(), destHost.begin(), destHost.end());
313 req.push_back((unsigned char)(destPort >> 8));
314 req.push_back((unsigned char)(destPort & 0xff));
315 if (!WriteN(s, &req[0], (int)req.size()))
316 return false;
317
318 unsigned char rep[4] = {0,0,0,0};
319 if (!ReadN(s, rep, sizeof(rep)) || rep[0] != 0x05)
320 return false;
321 if (rep[1] != 0x00)
322 {
323 error("SOCKS5 CONNECT to %s:%u failed with reply 0x%02x\n",
324 destHost.c_str(), (unsigned)destPort, (unsigned)rep[1]);
325 return false;
326 }
327 int nAddr = 0;
328 if (rep[3] == 0x01) nAddr = 4;
329 else if (rep[3] == 0x04) nAddr = 16;
330 else if (rep[3] == 0x03)
331 {
332 unsigned char len = 0;
333 if (!ReadN(s, &len, 1))
334 return false;
335 nAddr = len;
336 }
337 else
338 return false;
339
340 std::vector<unsigned char> discard(nAddr + 2);
341 return ReadN(s, &discard[0], (int)discard.size());
342}
343
344SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort,
345 int nSockSite, int nTimeoutSecs)
346{
347 std::string proxyHost;
348 unsigned short proxyPort = 0;
349 bool fProxy = false;
350 CRITICAL_BLOCK(cs_socks5)
351 {
352 fProxy = g_fSocks5Proxy;
353 proxyHost = g_socks5Host;
354 proxyPort = g_socks5Port;
355 }
356
357 if (!fProxy)
358 {
359 if (BtfIsTorOnionHost(destHost))
360 {
361 error("Refusing direct .onion connection to %s:%u; use /tor or /socks\n",
362 destHost.c_str(), (unsigned)destPort);
363 return INVALID_SOCKET;
364 }
365 return ConnectDirect(destHost, destPort, nSockSite, nTimeoutSecs);
366 }
367
368 SOCKET s = ConnectDirect(proxyHost, proxyPort, nSockSite, nTimeoutSecs);
369 if (s == INVALID_SOCKET)
370 return INVALID_SOCKET;
371 if (!Socks5Connect(s, destHost, destPort))
372 {
373 BtfCloseSocket(s);
374 return INVALID_SOCKET;
375 }
376 return s;
377}