1 /*
2 * Copyright (c) 2007-2009, 2013-2015, 2018-2020 Paul Mattes.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the names of Paul Mattes nor the names of his contributors
13 * may be used to endorse or promote products derived from this software
14 * without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /*
29 * proxy.c
30 * This module implements various kinds of proxies.
31 */
32
33 #include "globals.h"
34
35 #if !defined(_WIN32) /*[*/
36 #include <sys/ioctl.h>
37 #include <netinet/in.h>
38 #include <netdb.h>
39 #include <arpa/inet.h>
40 #endif /*]*/
41
42 #include "popups.h"
43 #include "proxy.h"
44 #include "proxy_names.h"
45 #include "proxy_private.h"
46 #include "proxy_passthru.h"
47 #include "proxy_http.h"
48 #include "proxy_names.h"
49 #include "proxy_telnet.h"
50 #include "proxy_socks4.h"
51 #include "proxy_socks5.h"
52 #include "task.h"
53 #include "trace.h"
54 #include "utils.h"
55 #include "w3misc.h"
56
57 #define PROXY_MSEC (15 * 1000)
58
59 /* proxy type names -- keep these in sync with proxytype_t! */
60 const char *type_name[PT_MAX] = {
61 "unknown",
62 PROXY_PASSTHRU,
63 PROXY_HTTP,
64 PROXY_TELNET,
65 PROXY_SOCKS4,
66 PROXY_SOCKS4A,
67 PROXY_SOCKS5,
68 PROXY_SOCKS5D
69 };
70
71 int proxy_ports[PT_MAX] = {
72 0,
73 NPORT_PASSTHRU,
74 NPORT_HTTP,
75 0,
76 NPORT_SOCKS4,
77 NPORT_SOCKS4A,
78 NPORT_SOCKS5,
79 NPORT_SOCKS5D
80 };
81
82 static bool parse_host_port(char *s, char **puser, char **phost, char **pport);
83
84 /* Continue functions. */
85 static continue_t *continues[PT_MAX] = {
86 NULL,
87 NULL,
88 proxy_http_continue,
89 NULL,
90 proxy_socks4_continue,
91 proxy_socks4_continue,
92 proxy_socks5_continue,
93 proxy_socks5_continue
94 };
95
96 /* Close functions. */
97 typedef void close_t(void);
98 static close_t *closes[PT_MAX] = {
99 NULL,
100 NULL,
101 proxy_http_close,
102 NULL,
103 proxy_socks4_close,
104 proxy_socks4_close,
105 proxy_socks5_close,
106 proxy_socks5_close
107 };
108
109 static proxytype_t proxy_type = PT_NONE;
110 static bool proxy_pending = false;
111 static ioid_t proxy_timeout_id = NULL_IOID;
112
113 /* Return the name for a given proxy type. */
114 const char *
proxy_type_name(proxytype_t type)115 proxy_type_name(proxytype_t type)
116 {
117 if (type <= PT_NONE || type >= PT_MAX) {
118 return "unknown";
119 } else {
120 return type_name[type];
121 }
122 }
123
124 /* Return whether a proxy type accepts a username. */
125 bool
proxy_takes_username(proxytype_t type)126 proxy_takes_username(proxytype_t type)
127 {
128 switch (type) {
129 case PT_HTTP:
130 case PT_SOCKS4:
131 case PT_SOCKS4A:
132 case PT_SOCKS5:
133 case PT_SOCKS5D:
134 return true;
135 default:
136 return false;
137 }
138 }
139
140 /* Return the default port for a proxy type. */
141 int
proxy_default_port(proxytype_t type)142 proxy_default_port(proxytype_t type)
143 {
144 if (type <= PT_NONE || type >= PT_MAX) {
145 return 0;
146 } else {
147 return proxy_ports[type];
148 }
149 }
150
151 /*
152 * Resolve the type, hostname and port for a proxy.
153 * Returns -1 for failure, 0 for no proxy, >0 (the proxy type) for success.
154 */
155 int
proxy_setup(const char * proxy,char ** puser,char ** phost,char ** pport)156 proxy_setup(const char *proxy, char **puser, char **phost, char **pport)
157 {
158 char *colon;
159 size_t sl;
160
161 if (proxy == NULL) {
162 return PT_NONE;
163 }
164
165 if ((colon = strchr(proxy, ':')) == NULL || (colon == proxy)) {
166 popup_an_error("Invalid proxy syntax");
167 return -1;
168 }
169
170 sl = colon - proxy;
171 if (sl == strlen(PROXY_PASSTHRU) &&
172 !strncasecmp(proxy, PROXY_PASSTHRU, sl)) {
173
174 if (!parse_host_port(colon + 1, NULL, phost, pport)) {
175 return -1;
176 }
177 if (*pport == NULL) {
178 *pport = NewString(PORT_PASSTHRU);
179 }
180 return proxy_type = PT_PASSTHRU;
181 }
182 if (sl == strlen(PROXY_HTTP) && !strncasecmp(proxy, PROXY_HTTP, sl)) {
183
184 if (!parse_host_port(colon + 1, puser, phost, pport)) {
185 return -1;
186 }
187 if (*pport == NULL) {
188 *pport = NewString(PORT_HTTP);
189 }
190 return proxy_type = PT_HTTP;
191 }
192 if (sl == strlen(PROXY_TELNET) && !strncasecmp(proxy, PROXY_TELNET, sl)) {
193
194 if (!parse_host_port(colon + 1, NULL, phost, pport)) {
195 return -1;
196 }
197 if (*pport == NULL) {
198 popup_an_error("Must specify port for telnet proxy");
199 return -1;
200 }
201 return proxy_type = PT_TELNET;
202 }
203 if (sl == strlen(PROXY_SOCKS4) && !strncasecmp(proxy, PROXY_SOCKS4, sl)) {
204
205 if (!parse_host_port(colon + 1, puser, phost, pport)) {
206 return -1;
207 }
208 if (*pport == NULL) {
209 *pport = NewString(PORT_SOCKS4);
210 }
211 return proxy_type = PT_SOCKS4;
212 }
213 if (sl == strlen(PROXY_SOCKS4A) &&
214 !strncasecmp(proxy, PROXY_SOCKS4A, sl)) {
215
216 if (!parse_host_port(colon + 1, puser, phost, pport)) {
217 return -1;
218 }
219 if (*pport == NULL) {
220 *pport = NewString(PORT_SOCKS4A);
221 }
222 return proxy_type = PT_SOCKS4A;
223 }
224 if (sl == strlen(PROXY_SOCKS5) && !strncasecmp(proxy, PROXY_SOCKS5, sl)) {
225
226 if (!parse_host_port(colon + 1, puser, phost, pport)) {
227 return -1;
228 }
229 if (*pport == NULL) {
230 *pport = NewString(PORT_SOCKS5);
231 }
232 return proxy_type = PT_SOCKS5;
233 }
234 if (sl == strlen(PROXY_SOCKS5D) &&
235 !strncasecmp(proxy, PROXY_SOCKS5D, sl)) {
236
237 if (!parse_host_port(colon + 1, puser, phost, pport)) {
238 return -1;
239 }
240 if (*pport == NULL) {
241 *pport = NewString(PORT_SOCKS5D);
242 }
243 return proxy_type = PT_SOCKS5D;
244 }
245 popup_an_error("Invalid proxy type '%.*s'", (int)sl, proxy);
246 return -1;
247 }
248
249 /*
250 * Parse [user:password@]host[:port] from a string.
251 * 'host' can be in square brackets to allow numeric IPv6 addresses.
252 * Returns the host name and port name in heap memory.
253 * Returns false for failure, true for success.
254 */
255 static bool
parse_host_port(char * s,char ** puser,char ** phost,char ** pport)256 parse_host_port(char *s, char **puser, char **phost, char **pport)
257 {
258 char *at;
259 char *h;
260 char *colon;
261 char *hstart;
262 size_t hlen;
263
264 /* Check for 'username:password@' first. */
265 if ((at = strchr(s, '@')) != NULL) {
266 if (puser == NULL) {
267 popup_an_error("Proxy type does not support username");
268 return false;
269 }
270 if (at == s) {
271 popup_an_error("Invalid proxy username syntax");
272 return false;
273 }
274 h = at + 1;
275 } else {
276 h = s;
277 }
278
279 if (*h == '[') {
280 char *rbrack;
281
282 /* Hostname in square brackets. */
283 hstart = h + 1;
284 rbrack = strchr(h, ']');
285 if (rbrack == NULL || rbrack == h + 1 ||
286 (*(rbrack + 1) != '\0' && *(rbrack + 1) != ':')) {
287 popup_an_error("Invalid proxy hostname syntax");
288 return false;
289 }
290 if (*(rbrack + 1) == ':') {
291 colon = rbrack + 1;
292 } else {
293 colon = NULL;
294 }
295 hlen = rbrack - (h + 1);
296 } else {
297 hstart = h;
298 colon = strchr(h, ':');
299 if (colon == h) {
300 popup_an_error("Invalid proxy hostname syntax");
301 return false;
302 }
303 if (colon == NULL) {
304 hlen = strlen(h);
305 } else {
306 hlen = colon - h;
307 }
308 }
309
310 /* Parse the port. */
311 if (colon == NULL || !*(colon + 1)) {
312 *pport = NULL;
313 } else {
314 *pport = NewString(colon + 1);
315 }
316
317 /* Copy out the hostname. */
318 *phost = Malloc(hlen + 1);
319 strncpy(*phost, hstart, hlen);
320 (*phost)[hlen] = '\0';
321
322 /* Copy out the username. */
323 if (puser != NULL) {
324 if (at != NULL) {
325 *puser = Malloc((at - s) + 1);
326 strncpy(*puser, s, at - s);
327 (*puser)[at - s] = '\0';
328 } else {
329 *puser = NULL;
330 }
331 } else if (at != NULL) {
332 popup_an_error("Invalid proxy hostname syntax (user@ not supported for this type");
333 return false;
334 }
335
336 return true;
337 }
338
339 /*
340 * Proxy negotiation timed out.
341 */
342 static void
proxy_timeout(ioid_t id _is_unused)343 proxy_timeout(ioid_t id _is_unused)
344 {
345 proxy_timeout_id = NULL_IOID;
346 connect_error("%s proxy timed out", type_name[proxy_type]);
347 }
348
349 /*
350 * Negotiate with the proxy server.
351 */
352 proxy_negotiate_ret_t
proxy_negotiate(socket_t fd,const char * user,const char * host,unsigned short port,bool blocking)353 proxy_negotiate(socket_t fd, const char *user, const char *host,
354 unsigned short port, bool blocking)
355 {
356 proxy_negotiate_ret_t ret;
357
358 if (proxy_timeout_id != NULL_IOID) {
359 RemoveTimeOut(proxy_timeout_id);
360 proxy_timeout_id = NULL_IOID;
361 }
362
363 switch (proxy_type) {
364 case PT_NONE:
365 ret = PX_SUCCESS;
366 break;
367 case PT_PASSTHRU:
368 ret = proxy_passthru(fd, host, port);
369 break;
370 case PT_HTTP:
371 ret = proxy_http(fd, user, host, port);
372 break;
373 case PT_TELNET:
374 ret = proxy_telnet(fd, host, port);
375 break;
376 case PT_SOCKS4:
377 ret = proxy_socks4(fd, user, host, port, false);
378 break;
379 case PT_SOCKS4A:
380 ret = proxy_socks4(fd, user, host, port, true);
381 break;
382 case PT_SOCKS5:
383 ret = proxy_socks5(fd, user, host, port, false);
384 break;
385 case PT_SOCKS5D:
386 ret = proxy_socks5(fd, user, host, port, true);
387 break;
388 default:
389 ret = PX_FAILURE;
390 break;
391 }
392
393 proxy_pending = (ret == PX_WANTMORE);
394 if (proxy_pending) {
395 if (blocking) {
396 do {
397 fd_set rfds;
398 struct timeval tv;
399
400 /* Wait for more input. */
401 FD_ZERO(&rfds);
402 FD_SET(fd, &rfds);
403 tv.tv_sec = PROXY_MSEC / 1000;
404 tv.tv_usec = (PROXY_MSEC % 1000) * 10;
405 if (select((int)(fd + 1), &rfds, NULL, NULL, &tv) <= 0) {
406 popup_an_error("%s proxy timeout", type_name[proxy_type]);
407 return PX_FAILURE;
408 }
409
410 ret = proxy_continue();
411 } while (ret == PX_WANTMORE);
412 } else {
413 /* Set a timeout in case the input never arrives. */
414 proxy_timeout_id = AddTimeOut(PROXY_MSEC, proxy_timeout);
415 }
416 }
417
418 if (ret == PX_SUCCESS) {
419 proxy_close();
420 }
421 return ret;
422 }
423
424 /*
425 * Continue proxy negotiation.
426 */
427 proxy_negotiate_ret_t
proxy_continue(void)428 proxy_continue(void)
429 {
430 proxy_negotiate_ret_t ret;
431
432 if (proxy_type <= 0 ||
433 proxy_type >= PT_MAX ||
434 continues[proxy_type] == NULL ||
435 !proxy_pending) {
436 popup_an_error("proxy_continue: wrong state");
437 return PX_FAILURE;
438 }
439
440 ret = (*continues[proxy_type])();
441 if (ret == PX_SUCCESS) {
442 proxy_close();
443 }
444 return ret;
445 }
446
447 /*
448 * Clean up pending proxy state.
449 */
450 void
proxy_close(void)451 proxy_close(void)
452 {
453 if (proxy_type > 0 &&
454 proxy_type < PT_MAX &&
455 closes[proxy_type] != NULL) {
456 (*closes[proxy_type])();
457 }
458 proxy_type = PT_NONE;
459 proxy_pending = false;
460 if (proxy_timeout_id != NULL_IOID) {
461 RemoveTimeOut(proxy_timeout_id);
462 proxy_timeout_id = NULL_IOID;
463 }
464 }
465