/src/httrack/src/htsproxy.c
Line | Count | Source |
1 | | /* ------------------------------------------------------------ */ |
2 | | /* |
3 | | HTTrack Website Copier, Offline Browser for Windows and Unix |
4 | | Copyright (C) 2026 Xavier Roche and other contributors |
5 | | |
6 | | SPDX-License-Identifier: GPL-3.0-or-later |
7 | | |
8 | | This program is free software: you can redistribute it and/or modify |
9 | | it under the terms of the GNU General Public License as published by |
10 | | the Free Software Foundation, either version 3 of the License, or |
11 | | (at your option) any later version. |
12 | | |
13 | | This program is distributed in the hope that it will be useful, |
14 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | | GNU General Public License for more details. |
17 | | |
18 | | You should have received a copy of the GNU General Public License |
19 | | along with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | | |
21 | | Ethical use: we kindly ask that you NOT use this software to harvest email |
22 | | addresses or to collect any other private information about people. Doing so |
23 | | would dishonor our work and waste the many hours we have spent on it. |
24 | | |
25 | | Please visit our Website: http://www.httrack.com |
26 | | */ |
27 | | |
28 | | /* ------------------------------------------------------------ */ |
29 | | /* File: Proxy tunneling (HTTP CONNECT, SOCKS5) */ |
30 | | /* Author: Xavier Roche */ |
31 | | /* ------------------------------------------------------------ */ |
32 | | |
33 | | /* Internal engine bytecode */ |
34 | | #define HTS_INTERNAL_BYTECODE |
35 | | |
36 | | #include "htscore.h" |
37 | | |
38 | | #include "htslib.h" |
39 | | #include "htsproxy.h" |
40 | | |
41 | | #include <string.h> |
42 | | |
43 | | // Read a CRLF line from a non-blocking socket (waits up to timeout per recv). |
44 | | // Returns the line length (0 = empty), or -1 on timeout/EOF/error. |
45 | 0 | static int proxy_getline(T_SOC soc, char *s, int max, int timeout) { |
46 | 0 | int j = 0; |
47 | |
|
48 | 0 | for (;;) { |
49 | 0 | unsigned char ch; |
50 | 0 | int n; |
51 | |
|
52 | 0 | if (!check_readinput_t(soc, timeout)) |
53 | 0 | return -1; // timed out waiting for data |
54 | 0 | n = (int) recv(soc, &ch, 1, 0); |
55 | 0 | if (n == 1) { |
56 | 0 | if (ch == 13) // CR |
57 | 0 | continue; |
58 | 0 | if (ch == 10) // LF: end of line |
59 | 0 | break; |
60 | 0 | if (j >= max - 1) |
61 | 0 | return -1; // line too long: bound the read against a hostile proxy |
62 | 0 | s[j++] = (char) ch; |
63 | 0 | } else if (n == 0) { |
64 | 0 | return -1; // connection closed |
65 | 0 | } else { |
66 | | #ifdef _WIN32 |
67 | | if (WSAGetLastError() == WSAEWOULDBLOCK) |
68 | | continue; |
69 | | #else |
70 | 0 | if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) |
71 | 0 | continue; |
72 | 0 | #endif |
73 | 0 | return -1; |
74 | 0 | } |
75 | 0 | } |
76 | 0 | s[j] = '\0'; |
77 | 0 | return j; |
78 | 0 | } |
79 | | |
80 | | int http_proxy_tunnel(httrackp *opt, htsblk *retour, const char *adr, |
81 | 0 | int timeout) { |
82 | 0 | const T_SOC soc = retour->soc; |
83 | 0 | const char *const host = jump_identification_const(adr); // host[:port] |
84 | 0 | const char *const portsep = jump_toport_const(adr); // ":port" or NULL |
85 | 0 | char BIGSTK authority[HTS_URLMAXSIZE * 2]; |
86 | 0 | char BIGSTK req[HTS_URLMAXSIZE * 4 + 1100]; |
87 | 0 | char line[1024]; |
88 | 0 | int code; |
89 | |
|
90 | 0 | if (soc == INVALID_SOCKET) |
91 | 0 | return 0; |
92 | | |
93 | | // CONNECT needs an explicit host:port; default :80 for http, :443 for https |
94 | 0 | authority[0] = '\0'; |
95 | 0 | if (portsep != NULL) |
96 | 0 | strlcatbuff(authority, host, sizeof(authority)); // already host:port |
97 | 0 | else { |
98 | 0 | const int defport = (strncmp(adr, "https://", 8) == 0) ? 443 : 80; |
99 | |
|
100 | 0 | snprintf(authority, sizeof(authority), "%s:%d", host, defport); |
101 | 0 | } |
102 | | |
103 | | // backstop: never let a stray CR/LF in the host smuggle a second line into |
104 | | // the CONNECT request (the host is already sanitized upstream) |
105 | 0 | if (!hts_is_control_free(authority)) { |
106 | 0 | strcpybuff(retour->msg, "proxy CONNECT: invalid host"); |
107 | 0 | return 0; |
108 | 0 | } |
109 | | |
110 | 0 | snprintf(req, sizeof(req), "CONNECT %s HTTP/1.0" H_CRLF "Host: %s" H_CRLF, |
111 | 0 | authority, authority); |
112 | | |
113 | | // creds go on the CONNECT, not the tunneled origin request |
114 | 0 | if (link_has_authorization(retour->req.proxy.name)) { |
115 | 0 | const char *a = jump_identification_const(retour->req.proxy.name); |
116 | 0 | const char *astart = jump_protocol_const(retour->req.proxy.name); |
117 | 0 | char autorisation[1100]; |
118 | 0 | char user_pass[256]; |
119 | |
|
120 | 0 | autorisation[0] = user_pass[0] = '\0'; |
121 | 0 | strncatbuff(user_pass, astart, (int) (a - astart) - 1); |
122 | 0 | strcpybuff(user_pass, unescape_http(OPT_GET_BUFF(opt), |
123 | 0 | OPT_GET_BUFF_SIZE(opt), user_pass)); |
124 | 0 | code64((unsigned char *) user_pass, (int) strlen(user_pass), |
125 | 0 | (unsigned char *) autorisation, 0); |
126 | 0 | strlcatbuff(req, "Proxy-Authorization: Basic ", sizeof(req)); |
127 | 0 | strlcatbuff(req, autorisation, sizeof(req)); |
128 | 0 | strlcatbuff(req, H_CRLF, sizeof(req)); |
129 | 0 | } |
130 | 0 | strlcatbuff(req, H_CRLF, sizeof(req)); // end of request headers |
131 | | |
132 | | // raw send(): sendc() would route to TLS when ssl is set (https tunnel) |
133 | 0 | { |
134 | 0 | const char *p = req; |
135 | 0 | size_t remain = strlen(req); |
136 | 0 | int stalls = 0; |
137 | |
|
138 | 0 | while (remain > 0) { |
139 | 0 | const int n = (int) send(soc, p, (int) remain, 0); |
140 | |
|
141 | 0 | if (n > 0) { |
142 | 0 | p += n; |
143 | 0 | remain -= (size_t) n; |
144 | 0 | stalls = 0; |
145 | 0 | } else { |
146 | | #ifdef _WIN32 |
147 | | const int wouldblock = (WSAGetLastError() == WSAEWOULDBLOCK); |
148 | | #else |
149 | 0 | const int wouldblock = |
150 | 0 | (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR); |
151 | 0 | #endif |
152 | | // don't spin forever on a fatal error or an unwritable socket |
153 | 0 | if (!wouldblock || !check_writeinput_t(soc, timeout) || |
154 | 0 | ++stalls > 100) { |
155 | 0 | strcpybuff(retour->msg, "proxy CONNECT: write error"); |
156 | 0 | return 0; |
157 | 0 | } |
158 | 0 | } |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | | // proxy status line: "HTTP/1.x <code> ..." |
163 | 0 | if (proxy_getline(soc, line, sizeof(line), timeout) < 0) { |
164 | 0 | strcpybuff(retour->msg, "proxy CONNECT: no response"); |
165 | 0 | return 0; |
166 | 0 | } |
167 | 0 | if (sscanf(line, "HTTP/%*d.%*d %d", &code) < 1) |
168 | 0 | code = 0; |
169 | 0 | if (code < 200 || code >= 300) { |
170 | 0 | htsblk_failf(retour, "proxy CONNECT refused: %s", |
171 | 0 | strnotempty(line) ? line : "(no status)"); |
172 | 0 | return 0; |
173 | 0 | } |
174 | | |
175 | | // drain headers to the blank line; cap the count so a flooding proxy can't |
176 | | // stall the crawl |
177 | 0 | { |
178 | 0 | int headers = 0; |
179 | |
|
180 | 0 | for (;;) { |
181 | 0 | const int n = proxy_getline(soc, line, sizeof(line), timeout); |
182 | |
|
183 | 0 | if (n < 0) { |
184 | 0 | strcpybuff(retour->msg, "proxy CONNECT: truncated response"); |
185 | 0 | return 0; |
186 | 0 | } |
187 | 0 | if (n == 0) |
188 | 0 | break; // blank line: tunnel ready |
189 | 0 | if (++headers > 64) { |
190 | 0 | strcpybuff(retour->msg, "proxy CONNECT: too many response headers"); |
191 | 0 | return 0; |
192 | 0 | } |
193 | 0 | } |
194 | 0 | } |
195 | | |
196 | 0 | return 1; |
197 | 0 | } |
198 | | |
199 | | /* SOCKS5 client (RFC 1928, RFC 1929 auth), hostname mode only: the proxy |
200 | | resolves the origin name (remote DNS, curl's socks5h). The stream is the |
201 | | proxy socket, or a scripted buffer under -#test=socks5. */ |
202 | | |
203 | 0 | #define SOCKS5_VERSION 0x05 |
204 | 0 | #define SOCKS5_AUTH_VERSION 0x01 |
205 | 0 | #define SOCKS5_METHOD_NONE 0x00 |
206 | 0 | #define SOCKS5_METHOD_USERPASS 0x02 |
207 | 0 | #define SOCKS5_METHOD_NOACCEPTABLE 0xFF |
208 | 0 | #define SOCKS5_CMD_CONNECT 0x01 |
209 | 0 | #define SOCKS5_ATYP_IPV4 0x01 |
210 | 0 | #define SOCKS5_ATYP_DOMAIN 0x03 |
211 | 0 | #define SOCKS5_ATYP_IPV6 0x04 |
212 | 0 | #define SOCKS5_MAXFIELD 255 /* one length byte: host, user and password */ |
213 | | |
214 | | typedef struct socks5_stream { |
215 | | T_SOC soc; /* INVALID_SOCKET when scripted (self-test) */ |
216 | | int timeout; |
217 | | socks5_test_io *io; |
218 | | } socks5_stream; |
219 | | |
220 | 0 | static int socks5_fail(char *msg, size_t msgsize, const char *text) { |
221 | 0 | if (msgsize != 0) |
222 | 0 | strlcpybuff(msg, text, msgsize); |
223 | 0 | return 0; |
224 | 0 | } |
225 | | |
226 | | /* Read exactly n bytes, or fail: SOCKS frames are not self-delimiting, so a |
227 | | short read would desync the stream shared with the origin traffic. */ |
228 | 0 | static int socks5_read_n(socks5_stream *st, unsigned char *buf, size_t n) { |
229 | 0 | size_t got = 0; |
230 | 0 | int stalls = 0; |
231 | |
|
232 | 0 | if (st->soc == INVALID_SOCKET) { /* scripted */ |
233 | 0 | socks5_test_io *const io = st->io; |
234 | |
|
235 | 0 | if (n > io->reply_len - io->consumed) |
236 | 0 | return 0; |
237 | 0 | memcpy(buf, io->reply + io->consumed, n); |
238 | 0 | io->consumed += n; |
239 | 0 | return 1; |
240 | 0 | } |
241 | 0 | while (got < n) { |
242 | 0 | const int r = (int) recv(st->soc, (char *) buf + got, (int) (n - got), 0); |
243 | |
|
244 | 0 | if (r > 0) { |
245 | 0 | got += (size_t) r; |
246 | 0 | stalls = 0; |
247 | 0 | } else if (r == 0) { |
248 | 0 | return 0; // proxy closed mid-frame |
249 | 0 | } else { |
250 | | #ifdef _WIN32 |
251 | | const int wouldblock = (WSAGetLastError() == WSAEWOULDBLOCK); |
252 | | #else |
253 | 0 | const int wouldblock = |
254 | 0 | (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR); |
255 | 0 | #endif |
256 | 0 | if (!wouldblock || !check_readinput_t(st->soc, st->timeout) || |
257 | 0 | ++stalls > 100) |
258 | 0 | return 0; |
259 | 0 | } |
260 | 0 | } |
261 | 0 | return 1; |
262 | 0 | } |
263 | | |
264 | | static int socks5_write_all(socks5_stream *st, const unsigned char *buf, |
265 | 0 | size_t len) { |
266 | 0 | size_t remain = len; |
267 | 0 | int stalls = 0; |
268 | |
|
269 | 0 | if (st->soc == INVALID_SOCKET) { /* scripted */ |
270 | 0 | socks5_test_io *const io = st->io; |
271 | |
|
272 | 0 | if (len > sizeof(io->sent) - io->sent_len) |
273 | 0 | return 0; |
274 | 0 | memcpy(io->sent + io->sent_len, buf, len); |
275 | 0 | io->sent_len += len; |
276 | 0 | return 1; |
277 | 0 | } |
278 | 0 | while (remain > 0) { |
279 | | // raw send: the socket is still plain here, sendc() would route to TLS |
280 | 0 | const int n = (int) send(st->soc, (const char *) buf, (int) remain, 0); |
281 | |
|
282 | 0 | if (n > 0) { |
283 | 0 | buf += n; |
284 | 0 | remain -= (size_t) n; |
285 | 0 | stalls = 0; |
286 | 0 | } else { |
287 | | #ifdef _WIN32 |
288 | | const int wouldblock = (WSAGetLastError() == WSAEWOULDBLOCK); |
289 | | #else |
290 | 0 | const int wouldblock = |
291 | 0 | (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR); |
292 | 0 | #endif |
293 | 0 | if (!wouldblock || !check_writeinput_t(st->soc, st->timeout) || |
294 | 0 | ++stalls > 100) |
295 | 0 | return 0; |
296 | 0 | } |
297 | 0 | } |
298 | 0 | return 1; |
299 | 0 | } |
300 | | |
301 | 0 | static const char *socks5_rep_message(unsigned char rep) { |
302 | 0 | switch (rep) { |
303 | 0 | case 0x01: |
304 | 0 | return "general failure"; |
305 | 0 | case 0x02: |
306 | 0 | return "connection not allowed by ruleset"; |
307 | 0 | case 0x03: |
308 | 0 | return "network unreachable"; |
309 | 0 | case 0x04: |
310 | 0 | return "host unreachable"; |
311 | 0 | case 0x05: |
312 | 0 | return "connection refused"; |
313 | 0 | case 0x06: |
314 | 0 | return "TTL expired"; |
315 | 0 | case 0x07: |
316 | 0 | return "command not supported"; |
317 | 0 | case 0x08: |
318 | 0 | return "address type not supported"; |
319 | 0 | default: |
320 | 0 | return "unknown error"; |
321 | 0 | } |
322 | 0 | } |
323 | | |
324 | | /* The reply's BND.ADDR is variable-length: consume exactly what ATYP says, or |
325 | | the leftover bytes prepend to the first origin (or TLS) read. */ |
326 | 0 | static int socks5_read_reply(socks5_stream *st, char *msg, size_t msgsize) { |
327 | 0 | unsigned char head[4]; |
328 | 0 | unsigned char addr[SOCKS5_MAXFIELD + 2]; |
329 | 0 | size_t addrlen; |
330 | |
|
331 | 0 | if (!socks5_read_n(st, head, sizeof(head))) |
332 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: no reply from proxy"); |
333 | 0 | if (head[0] != SOCKS5_VERSION) |
334 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: bad version in reply"); |
335 | 0 | if (head[1] != 0x00) { |
336 | 0 | snprintf(msg, msgsize, "SOCKS5 connect failed: %s", |
337 | 0 | socks5_rep_message(head[1])); |
338 | 0 | return 0; |
339 | 0 | } |
340 | 0 | switch (head[3]) { |
341 | 0 | case SOCKS5_ATYP_IPV4: |
342 | 0 | addrlen = 4; |
343 | 0 | break; |
344 | 0 | case SOCKS5_ATYP_IPV6: |
345 | 0 | addrlen = 16; |
346 | 0 | break; |
347 | 0 | case SOCKS5_ATYP_DOMAIN: { |
348 | 0 | unsigned char len; |
349 | |
|
350 | 0 | if (!socks5_read_n(st, &len, 1)) |
351 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: truncated reply"); |
352 | 0 | addrlen = len; // <= 255, always fits addr[] |
353 | 0 | break; |
354 | 0 | } |
355 | 0 | default: // unknown length: the stream cannot be resynchronized |
356 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: unknown address type in reply"); |
357 | 0 | } |
358 | 0 | if (addrlen != 0 && !socks5_read_n(st, addr, addrlen)) |
359 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: truncated reply address"); |
360 | 0 | if (!socks5_read_n(st, addr, 2)) // BND.PORT, unused |
361 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: truncated reply port"); |
362 | 0 | return 1; |
363 | 0 | } |
364 | | |
365 | | static int socks5_negotiate(socks5_stream *st, const char *host, size_t hostlen, |
366 | | int port, const char *user, size_t userlen, |
367 | | const char *pass, size_t passlen, int want_auth, |
368 | 0 | char *msg, size_t msgsize) { |
369 | 0 | unsigned char frame[3 + SOCKS5_MAXFIELD + SOCKS5_MAXFIELD]; |
370 | 0 | unsigned char rep[2]; |
371 | | |
372 | | /* greeting */ |
373 | 0 | frame[0] = SOCKS5_VERSION; |
374 | 0 | frame[1] = (unsigned char) (want_auth ? 2 : 1); |
375 | 0 | frame[2] = SOCKS5_METHOD_NONE; |
376 | 0 | frame[3] = SOCKS5_METHOD_USERPASS; |
377 | 0 | if (!socks5_write_all(st, frame, (size_t) 2 + frame[1])) |
378 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: write error"); |
379 | 0 | if (!socks5_read_n(st, rep, sizeof(rep))) |
380 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: no method reply from proxy"); |
381 | 0 | if (rep[0] != SOCKS5_VERSION) |
382 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: bad version in method reply"); |
383 | 0 | switch (rep[1]) { |
384 | 0 | case SOCKS5_METHOD_NONE: |
385 | 0 | break; |
386 | 0 | case SOCKS5_METHOD_USERPASS: |
387 | 0 | if (!want_auth) |
388 | 0 | return socks5_fail(msg, msgsize, |
389 | 0 | "SOCKS5: proxy requires authentication, none given"); |
390 | | /* RFC 1929 sub-negotiation; its version byte is 0x01, not 0x05 */ |
391 | 0 | frame[0] = SOCKS5_AUTH_VERSION; |
392 | 0 | frame[1] = (unsigned char) userlen; |
393 | 0 | memcpy(frame + 2, user, userlen); |
394 | 0 | frame[2 + userlen] = (unsigned char) passlen; |
395 | 0 | memcpy(frame + 3 + userlen, pass, passlen); |
396 | 0 | if (!socks5_write_all(st, frame, 3 + userlen + passlen)) |
397 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: write error"); |
398 | 0 | if (!socks5_read_n(st, rep, sizeof(rep))) |
399 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: no authentication reply"); |
400 | 0 | if (rep[1] != 0x00) |
401 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: authentication failed"); |
402 | 0 | break; |
403 | 0 | case SOCKS5_METHOD_NOACCEPTABLE: |
404 | 0 | return socks5_fail( |
405 | 0 | msg, msgsize, |
406 | 0 | "SOCKS5: proxy accepts no authentication method we offer"); |
407 | 0 | default: |
408 | 0 | return socks5_fail(msg, msgsize, |
409 | 0 | "SOCKS5: proxy selected an unknown method"); |
410 | 0 | } |
411 | | |
412 | | /* CONNECT to the origin, by name */ |
413 | 0 | frame[0] = SOCKS5_VERSION; |
414 | 0 | frame[1] = SOCKS5_CMD_CONNECT; |
415 | 0 | frame[2] = 0x00; // RSV |
416 | 0 | frame[3] = SOCKS5_ATYP_DOMAIN; |
417 | 0 | frame[4] = (unsigned char) hostlen; |
418 | 0 | memcpy(frame + 5, host, hostlen); |
419 | 0 | frame[5 + hostlen] = (unsigned char) (port >> 8); |
420 | 0 | frame[6 + hostlen] = (unsigned char) (port & 0xFF); |
421 | 0 | if (!socks5_write_all(st, frame, 7 + hostlen)) |
422 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: write error"); |
423 | | |
424 | 0 | return socks5_read_reply(st, msg, msgsize); |
425 | 0 | } |
426 | | |
427 | | /* Decode the proxy userinfo into the two RFC 1929 fields. Split on the first |
428 | | colon of the still-escaped string, so a %3A stays inside the username. */ |
429 | | static int socks5_credentials(httrackp *opt, const char *proxy_name, char *user, |
430 | | size_t user_size, size_t *userlen, char *pass, |
431 | | size_t pass_size, size_t *passlen, char *msg, |
432 | 0 | size_t msgsize) { |
433 | 0 | const char *const a = jump_identification_const(proxy_name); // past the '@' |
434 | 0 | const char *const astart = jump_protocol_const(proxy_name); |
435 | 0 | char userinfo[1024]; |
436 | 0 | char *colon; |
437 | |
|
438 | 0 | if (a <= astart || (size_t) (a - astart) - 1 >= sizeof(userinfo)) |
439 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: credentials too long"); |
440 | 0 | userinfo[0] = '\0'; |
441 | 0 | strncatbuff(userinfo, astart, (int) (a - astart) - 1); |
442 | 0 | colon = strchr(userinfo, ':'); |
443 | 0 | if (colon != NULL) |
444 | 0 | *colon++ = '\0'; |
445 | 0 | strlcpybuff( |
446 | 0 | user, unescape_http(OPT_GET_BUFF(opt), OPT_GET_BUFF_SIZE(opt), userinfo), |
447 | 0 | user_size); |
448 | 0 | strlcpybuff(pass, |
449 | 0 | colon != NULL ? unescape_http(OPT_GET_BUFF(opt), |
450 | 0 | OPT_GET_BUFF_SIZE(opt), colon) |
451 | 0 | : "", |
452 | 0 | pass_size); |
453 | 0 | *userlen = strlen(user); |
454 | 0 | *passlen = strlen(pass); |
455 | | // reject, never truncate: a clipped secret would authenticate as another one |
456 | 0 | if (*userlen > SOCKS5_MAXFIELD || *passlen > SOCKS5_MAXFIELD) |
457 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: credentials too long"); |
458 | 0 | if (*userlen == 0) |
459 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: empty proxy username"); |
460 | 0 | return 1; |
461 | 0 | } |
462 | | |
463 | | static int socks5_handshake_stream(httrackp *opt, socks5_stream *st, |
464 | | const char *adr, const char *proxy_name, |
465 | 0 | int ssl, char *msg, size_t msgsize) { |
466 | 0 | const char *const host = jump_identification_const(adr); |
467 | 0 | const char *const portsep = jump_toport_const(adr); |
468 | 0 | const size_t hostlen = |
469 | 0 | portsep != NULL ? (size_t) (portsep - host) : strlen(host); |
470 | | // sized for the whole userinfo: decoding shrinks, so an over-long credential |
471 | | // lands intact and is rejected below rather than silently clipped |
472 | 0 | char user[1024]; |
473 | 0 | char pass[1024]; |
474 | 0 | size_t userlen = 0, passlen = 0; |
475 | 0 | int want_auth = 0; |
476 | 0 | int port = ssl ? 443 : 80; |
477 | |
|
478 | 0 | if (hostlen == 0 || hostlen > SOCKS5_MAXFIELD) |
479 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: invalid origin host"); |
480 | 0 | if (host[0] == '[') // ATYP=domain cannot carry an IPv6 literal |
481 | 0 | return socks5_fail(msg, msgsize, |
482 | 0 | "SOCKS5: IPv6 literal origin is not supported"); |
483 | 0 | if (!hts_is_control_free_sized(host, hostlen)) |
484 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: invalid origin host"); |
485 | | // the old range check ran after sscanf("%d") had wrapped a huge value into a |
486 | | // plausible port (#614). An empty "host:" stays refused here, unlike the |
487 | | // direct path, as it was before #614. |
488 | 0 | if (portsep != NULL && !hts_parse_url_port(portsep + 1, &port)) |
489 | 0 | return socks5_fail(msg, msgsize, "SOCKS5: invalid origin port"); |
490 | 0 | if (link_has_authorization(proxy_name)) { |
491 | 0 | if (!socks5_credentials(opt, proxy_name, user, sizeof(user), &userlen, pass, |
492 | 0 | sizeof(pass), &passlen, msg, msgsize)) |
493 | 0 | return 0; |
494 | 0 | want_auth = 1; |
495 | 0 | } |
496 | 0 | return socks5_negotiate(st, host, hostlen, port, user, userlen, pass, passlen, |
497 | 0 | want_auth, msg, msgsize); |
498 | 0 | } |
499 | | |
500 | | int socks5_handshake(httrackp *opt, htsblk *retour, const char *adr, |
501 | 0 | int timeout) { |
502 | 0 | socks5_stream st; |
503 | | #if HTS_USEOPENSSL |
504 | | const int ssl = retour->ssl; |
505 | | #else |
506 | 0 | const int ssl = 0; |
507 | 0 | #endif |
508 | |
|
509 | 0 | if (retour->soc == INVALID_SOCKET) |
510 | 0 | return 0; |
511 | 0 | st.soc = retour->soc; |
512 | 0 | st.timeout = timeout; |
513 | 0 | st.io = NULL; |
514 | 0 | return socks5_handshake_stream(opt, &st, adr, retour->req.proxy.name, ssl, |
515 | 0 | retour->msg, sizeof(retour->msg)); |
516 | 0 | } |
517 | | |
518 | | int socks5_handshake_scripted(httrackp *opt, const char *adr, |
519 | 0 | const char *proxy_name, socks5_test_io *io) { |
520 | 0 | socks5_stream st; |
521 | |
|
522 | 0 | st.soc = INVALID_SOCKET; |
523 | 0 | st.timeout = 0; |
524 | 0 | st.io = io; |
525 | 0 | io->consumed = 0; |
526 | 0 | io->sent_len = 0; |
527 | 0 | io->msg[0] = '\0'; |
528 | 0 | return socks5_handshake_stream(opt, &st, adr, proxy_name, 0, io->msg, |
529 | 0 | sizeof(io->msg)); |
530 | 0 | } |