Branch data Line data Source code
1 : : /* GIO - GLib Input, Output and Streaming Library
2 : : *
3 : : * Copyright (C) 2008, 2010 Collabora, Ltd.
4 : : * Copyright (C) 2008 Nokia Corporation. All rights reserved.
5 : : *
6 : : * SPDX-License-Identifier: LGPL-2.1-or-later
7 : : *
8 : : * This library is free software; you can redistribute it and/or
9 : : * modify it under the terms of the GNU Lesser General Public
10 : : * License as published by the Free Software Foundation; either
11 : : * version 2.1 of the License, or (at your option) any later version.
12 : : *
13 : : * This library 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 GNU
16 : : * Lesser General Public License for more details.
17 : : *
18 : : * You should have received a copy of the GNU Lesser General
19 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
20 : : *
21 : : * Author: Youness Alaoui <youness.alaoui@collabora.co.uk
22 : : *
23 : : * Contributors:
24 : : * Nicolas Dufresne <nicolas.dufresne@collabora.co.uk>
25 : : */
26 : :
27 : : #include "config.h"
28 : :
29 : : #include "gsocks5proxy.h"
30 : :
31 : : #include <string.h>
32 : :
33 : : #include "giomodule.h"
34 : : #include "giomodule-priv.h"
35 : : #include "giostream.h"
36 : : #include "ginetaddress.h"
37 : : #include "ginputstream.h"
38 : : #include "glibintl.h"
39 : : #include "goutputstream.h"
40 : : #include "gproxy.h"
41 : : #include "gproxyaddress.h"
42 : : #include "gtask.h"
43 : :
44 : : #define SOCKS5_VERSION 0x05
45 : :
46 : : #define SOCKS5_CMD_CONNECT 0x01
47 : : #define SOCKS5_CMD_BIND 0x02
48 : : #define SOCKS5_CMD_UDP_ASSOCIATE 0x03
49 : :
50 : : #define SOCKS5_ATYP_IPV4 0x01
51 : : #define SOCKS5_ATYP_DOMAINNAME 0x03
52 : : #define SOCKS5_ATYP_IPV6 0x04
53 : :
54 : : #define SOCKS5_AUTH_VERSION 0x01
55 : :
56 : : #define SOCKS5_AUTH_NONE 0x00
57 : : #define SOCKS5_AUTH_GSSAPI 0x01
58 : : #define SOCKS5_AUTH_USR_PASS 0x02
59 : : #define SOCKS5_AUTH_NO_ACCEPT 0xff
60 : :
61 : : #define SOCKS5_MAX_LEN 255
62 : : #define SOCKS5_RESERVED 0x00
63 : :
64 : : #define SOCKS5_REP_SUCCEEDED 0x00
65 : : #define SOCKS5_REP_SRV_FAILURE 0x01
66 : : #define SOCKS5_REP_NOT_ALLOWED 0x02
67 : : #define SOCKS5_REP_NET_UNREACH 0x03
68 : : #define SOCKS5_REP_HOST_UNREACH 0x04
69 : : #define SOCKS5_REP_REFUSED 0x05
70 : : #define SOCKS5_REP_TTL_EXPIRED 0x06
71 : : #define SOCKS5_REP_CMD_NOT_SUP 0x07
72 : : #define SOCKS5_REP_ATYPE_NOT_SUP 0x08
73 : :
74 : :
75 : : struct _GSocks5Proxy
76 : : {
77 : : GObject parent;
78 : : };
79 : :
80 : : struct _GSocks5ProxyClass
81 : : {
82 : : GObjectClass parent_class;
83 : : };
84 : :
85 : : static void g_socks5_proxy_iface_init (GProxyInterface *proxy_iface);
86 : :
87 : : #define g_socks5_proxy_get_type _g_socks5_proxy_get_type
88 : 238 : G_DEFINE_TYPE_WITH_CODE (GSocks5Proxy, g_socks5_proxy, G_TYPE_OBJECT,
89 : : G_IMPLEMENT_INTERFACE (G_TYPE_PROXY,
90 : : g_socks5_proxy_iface_init)
91 : : _g_io_modules_ensure_extension_points_registered ();
92 : : g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME,
93 : : g_define_type_id,
94 : : "socks5",
95 : : 0))
96 : :
97 : : static void
98 : 0 : g_socks5_proxy_finalize (GObject *object)
99 : : {
100 : : /* must chain up */
101 : 0 : G_OBJECT_CLASS (g_socks5_proxy_parent_class)->finalize (object);
102 : 0 : }
103 : :
104 : : static void
105 : 0 : g_socks5_proxy_init (GSocks5Proxy *proxy)
106 : : {
107 : 0 : }
108 : :
109 : : /*
110 : : * +----+----------+----------+
111 : : * |VER | NMETHODS | METHODS |
112 : : * +----+----------+----------+
113 : : * | 1 | 1 | 1 to 255 |
114 : : * +----+----------+----------+
115 : : */
116 : : #define SOCKS5_NEGO_MSG_LEN 4
117 : : static size_t
118 : 0 : set_nego_msg (guint8 *msg, gboolean has_auth)
119 : : {
120 : 0 : size_t len = 3;
121 : :
122 : 0 : msg[0] = SOCKS5_VERSION;
123 : 0 : msg[1] = 0x01; /* number of methods supported */
124 : 0 : msg[2] = SOCKS5_AUTH_NONE;
125 : :
126 : : /* add support for authentication method */
127 : 0 : if (has_auth)
128 : : {
129 : 0 : msg[1] = 0x02; /* number of methods supported */
130 : 0 : msg[3] = SOCKS5_AUTH_USR_PASS;
131 : 0 : len++;
132 : : }
133 : :
134 : 0 : return len;
135 : : }
136 : :
137 : :
138 : : /*
139 : : * +----+--------+
140 : : * |VER | METHOD |
141 : : * +----+--------+
142 : : * | 1 | 1 |
143 : : * +----+--------+
144 : : */
145 : : #define SOCKS5_NEGO_REP_LEN 2
146 : : static gboolean
147 : 0 : parse_nego_reply (const guint8 *data,
148 : : gboolean has_auth,
149 : : gboolean *must_auth,
150 : : GError **error)
151 : : {
152 : 0 : if (data[0] != SOCKS5_VERSION)
153 : : {
154 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
155 : : _("The server is not a SOCKSv5 proxy server."));
156 : 0 : return FALSE;
157 : : }
158 : :
159 : 0 : switch (data[1])
160 : : {
161 : 0 : case SOCKS5_AUTH_NONE:
162 : 0 : *must_auth = FALSE;
163 : 0 : break;
164 : :
165 : 0 : case SOCKS5_AUTH_USR_PASS:
166 : 0 : if (!has_auth)
167 : : {
168 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_NEED_AUTH,
169 : : _("The SOCKSv5 proxy requires authentication."));
170 : 0 : return FALSE;
171 : : }
172 : 0 : *must_auth = TRUE;
173 : 0 : break;
174 : :
175 : 0 : case SOCKS5_AUTH_NO_ACCEPT:
176 : 0 : if (!has_auth)
177 : : {
178 : : /* The server has said it accepts none of our authentication methods,
179 : : * but given the slightly odd implementation of set_nego_msg(), we
180 : : * actually only gave it the choice of %SOCKS5_AUTH_NONE, since the
181 : : * caller specified no username or password.
182 : : * Return %G_IO_ERROR_PROXY_NEED_AUTH so the caller knows that if
183 : : * they specify a username and password and try again, authentication
184 : : * might succeed (since we’ll send %SOCKS5_AUTH_USR_PASS next time). */
185 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_NEED_AUTH,
186 : : _("The SOCKSv5 proxy requires authentication."));
187 : 0 : return FALSE;
188 : : }
189 : : G_GNUC_FALLTHROUGH;
190 : : case SOCKS5_AUTH_GSSAPI:
191 : : default:
192 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_AUTH_FAILED,
193 : : _("The SOCKSv5 proxy requires an authentication "
194 : : "method that is not supported by GLib."));
195 : 0 : return FALSE;
196 : : break;
197 : : }
198 : :
199 : 0 : return TRUE;
200 : : }
201 : :
202 : : #define SOCKS5_AUTH_MSG_LEN 515
203 : : static gboolean
204 : 0 : set_auth_msg (guint8 *msg,
205 : : const gchar *username,
206 : : const gchar *password,
207 : : size_t *out_len,
208 : : GError **error)
209 : : {
210 : 0 : size_t len = 0;
211 : 0 : size_t ulen = 0; /* username length */
212 : 0 : size_t plen = 0; /* Password length */
213 : :
214 : : /* Clear output first */
215 : 0 : if (out_len != NULL)
216 : 0 : *out_len = 0;
217 : :
218 : 0 : if (username)
219 : 0 : ulen = strlen (username);
220 : :
221 : 0 : if (password)
222 : 0 : plen = strlen (password);
223 : :
224 : 0 : if (ulen > SOCKS5_MAX_LEN || plen > SOCKS5_MAX_LEN)
225 : : {
226 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
227 : : _("Username or password is too long for SOCKSv5 "
228 : : "protocol."));
229 : 0 : return FALSE;
230 : : }
231 : :
232 : 0 : msg[len++] = SOCKS5_AUTH_VERSION;
233 : 0 : msg[len++] = ulen;
234 : :
235 : 0 : if (ulen > 0)
236 : 0 : memcpy (msg + len, username, ulen);
237 : :
238 : 0 : len += ulen;
239 : 0 : msg[len++] = plen;
240 : :
241 : 0 : if (plen > 0)
242 : 0 : memcpy (msg + len, password, plen);
243 : :
244 : 0 : len += plen;
245 : :
246 : 0 : if (out_len != NULL)
247 : 0 : *out_len = len;
248 : :
249 : 0 : return TRUE;
250 : : }
251 : :
252 : :
253 : : static gboolean
254 : 0 : check_auth_status (const guint8 *data, GError **error)
255 : : {
256 : 0 : if (data[0] != SOCKS5_AUTH_VERSION
257 : 0 : || data[1] != SOCKS5_REP_SUCCEEDED)
258 : : {
259 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_AUTH_FAILED,
260 : : _("SOCKSv5 authentication failed due to wrong "
261 : : "username or password."));
262 : 0 : return FALSE;
263 : : }
264 : 0 : return TRUE;
265 : : }
266 : :
267 : : /*
268 : : * +----+-----+-------+------+----------+----------+
269 : : * |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
270 : : * +----+-----+-------+------+----------+----------+
271 : : * | 1 | 1 | X'00' | 1 | Variable | 2 |
272 : : * +----+-----+-------+------+----------+----------+
273 : : * DST.ADDR is a string with first byte being the size. So DST.ADDR may not be
274 : : * longer then 256 bytes.
275 : : */
276 : : #define SOCKS5_CONN_MSG_LEN 262
277 : : static gboolean
278 : 0 : set_connect_msg (guint8 *msg,
279 : : const gchar *hostname,
280 : : guint16 port,
281 : : size_t *out_len,
282 : : GError **error)
283 : : {
284 : 0 : size_t len = 0;
285 : :
286 : : /* Clear output first */
287 : 0 : if (out_len != NULL)
288 : 0 : *out_len = 0;
289 : :
290 : 0 : msg[len++] = SOCKS5_VERSION;
291 : 0 : msg[len++] = SOCKS5_CMD_CONNECT;
292 : 0 : msg[len++] = SOCKS5_RESERVED;
293 : :
294 : 0 : if (g_hostname_is_ip_address (hostname))
295 : : {
296 : 0 : GInetAddress *addr = g_inet_address_new_from_string (hostname);
297 : 0 : gsize addr_len = g_inet_address_get_native_size (addr);
298 : :
299 : : /* We are cheating for simplicity, here's the logic:
300 : : * 1 = IPV4 = 4 bytes / 4
301 : : * 4 = IPV6 = 16 bytes / 4 */
302 : 0 : msg[len++] = addr_len / 4;
303 : 0 : memcpy (msg + len, g_inet_address_to_bytes (addr), addr_len);
304 : 0 : len += addr_len;
305 : :
306 : 0 : g_object_unref (addr);
307 : : }
308 : : else
309 : : {
310 : 0 : gsize host_len = strlen (hostname);
311 : :
312 : 0 : if (host_len > SOCKS5_MAX_LEN)
313 : : {
314 : 0 : g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
315 : : _("Hostname “%s” is too long for SOCKSv5 protocol"),
316 : : hostname);
317 : 0 : return FALSE;
318 : : }
319 : :
320 : 0 : msg[len++] = SOCKS5_ATYP_DOMAINNAME;
321 : 0 : msg[len++] = (guint8) host_len;
322 : 0 : memcpy (msg + len, hostname, host_len);
323 : 0 : len += host_len;
324 : : }
325 : :
326 : : {
327 : 0 : guint16 hp = g_htons (port);
328 : 0 : memcpy (msg + len, &hp, 2);
329 : 0 : len += 2;
330 : : }
331 : :
332 : 0 : if (out_len != NULL)
333 : 0 : *out_len = len;
334 : :
335 : 0 : return TRUE;
336 : : }
337 : :
338 : : /*
339 : : * +----+-----+-------+------+----------+----------+
340 : : * |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
341 : : * +----+-----+-------+------+----------+----------+
342 : : * | 1 | 1 | X'00' | 1 | Variable | 2 |
343 : : * +----+-----+-------+------+----------+----------+
344 : : * This reply need to be read by small part to determine size. Buffer
345 : : * size is determined in function of the biggest part to read.
346 : : *
347 : : * The parser only requires 4 bytes.
348 : : */
349 : : #define SOCKS5_CONN_REP_LEN 257
350 : : static gboolean
351 : 0 : parse_connect_reply (const guint8 *data, gint *atype, GError **error)
352 : : {
353 : 0 : if (data[0] != SOCKS5_VERSION)
354 : : {
355 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
356 : : _("The server is not a SOCKSv5 proxy server."));
357 : 0 : return FALSE;
358 : : }
359 : :
360 : 0 : switch (data[1])
361 : : {
362 : 0 : case SOCKS5_REP_SUCCEEDED:
363 : 0 : if (data[2] != SOCKS5_RESERVED)
364 : : {
365 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
366 : : _("The server is not a SOCKSv5 proxy server."));
367 : 0 : return FALSE;
368 : : }
369 : :
370 : 0 : switch (data[3])
371 : : {
372 : 0 : case SOCKS5_ATYP_IPV4:
373 : : case SOCKS5_ATYP_IPV6:
374 : : case SOCKS5_ATYP_DOMAINNAME:
375 : 0 : *atype = data[3];
376 : 0 : break;
377 : :
378 : 0 : default:
379 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
380 : : _("The SOCKSv5 proxy server uses unknown address type."));
381 : 0 : return FALSE;
382 : : }
383 : 0 : break;
384 : :
385 : 0 : case SOCKS5_REP_SRV_FAILURE:
386 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
387 : : _("Internal SOCKSv5 proxy server error."));
388 : 0 : return FALSE;
389 : : break;
390 : :
391 : 0 : case SOCKS5_REP_NOT_ALLOWED:
392 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_NOT_ALLOWED,
393 : : _("SOCKSv5 connection not allowed by ruleset."));
394 : 0 : return FALSE;
395 : : break;
396 : :
397 : 0 : case SOCKS5_REP_TTL_EXPIRED:
398 : : case SOCKS5_REP_HOST_UNREACH:
399 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE,
400 : : _("Host unreachable through SOCKSv5 server."));
401 : 0 : return FALSE;
402 : : break;
403 : :
404 : 0 : case SOCKS5_REP_NET_UNREACH:
405 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NETWORK_UNREACHABLE,
406 : : _("Network unreachable through SOCKSv5 proxy."));
407 : 0 : return FALSE;
408 : : break;
409 : :
410 : 0 : case SOCKS5_REP_REFUSED:
411 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
412 : : _("Connection refused through SOCKSv5 proxy."));
413 : 0 : return FALSE;
414 : : break;
415 : :
416 : 0 : case SOCKS5_REP_CMD_NOT_SUP:
417 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
418 : : _("SOCKSv5 proxy does not support “connect” command."));
419 : 0 : return FALSE;
420 : : break;
421 : :
422 : 0 : case SOCKS5_REP_ATYPE_NOT_SUP:
423 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
424 : : _("SOCKSv5 proxy does not support provided address type."));
425 : 0 : return FALSE;
426 : : break;
427 : :
428 : 0 : default: /* Unknown error */
429 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED,
430 : : _("Unknown SOCKSv5 proxy error."));
431 : 0 : return FALSE;
432 : : break;
433 : : }
434 : :
435 : 0 : return TRUE;
436 : : }
437 : :
438 : : static GIOStream *
439 : 0 : g_socks5_proxy_connect (GProxy *proxy,
440 : : GIOStream *io_stream,
441 : : GProxyAddress *proxy_address,
442 : : GCancellable *cancellable,
443 : : GError **error)
444 : : {
445 : : gboolean has_auth;
446 : : GInputStream *in;
447 : : GOutputStream *out;
448 : : const gchar *hostname;
449 : : guint16 port;
450 : : const gchar *username;
451 : : const gchar *password;
452 : :
453 : 0 : hostname = g_proxy_address_get_destination_hostname (proxy_address);
454 : 0 : port = g_proxy_address_get_destination_port (proxy_address);
455 : 0 : username = g_proxy_address_get_username (proxy_address);
456 : 0 : password = g_proxy_address_get_password (proxy_address);
457 : :
458 : 0 : has_auth = username || password;
459 : :
460 : 0 : in = g_io_stream_get_input_stream (io_stream);
461 : 0 : out = g_io_stream_get_output_stream (io_stream);
462 : :
463 : : /* Send SOCKS5 handshake */
464 : : {
465 : : guint8 msg[SOCKS5_NEGO_MSG_LEN];
466 : : size_t len;
467 : :
468 : 0 : len = set_nego_msg (msg, has_auth);
469 : :
470 : 0 : if (!g_output_stream_write_all (out, msg, len, NULL,
471 : : cancellable, error))
472 : 0 : goto error;
473 : : }
474 : :
475 : : /* Receive SOCKS5 response and reply with authentication if required */
476 : : {
477 : : guint8 data[SOCKS5_NEGO_REP_LEN];
478 : 0 : gboolean must_auth = FALSE;
479 : :
480 : 0 : if (!g_input_stream_read_all (in, data, sizeof (data), NULL,
481 : : cancellable, error))
482 : 0 : goto error;
483 : :
484 : 0 : if (!parse_nego_reply (data, has_auth, &must_auth, error))
485 : 0 : goto error;
486 : :
487 : 0 : if (must_auth)
488 : : {
489 : : guint8 msg[SOCKS5_AUTH_MSG_LEN];
490 : : size_t len;
491 : :
492 : 0 : if (!set_auth_msg (msg, username, password, &len, error))
493 : 0 : goto error;
494 : :
495 : 0 : if (!g_output_stream_write_all (out, msg, len, NULL,
496 : : cancellable, error))
497 : 0 : goto error;
498 : :
499 : 0 : if (!g_input_stream_read_all (in, data, sizeof (data), NULL,
500 : : cancellable, error))
501 : 0 : goto error;
502 : :
503 : 0 : if (!check_auth_status (data, error))
504 : 0 : goto error;
505 : : }
506 : : }
507 : :
508 : : /* Send SOCKS5 connection request */
509 : : {
510 : : guint8 msg[SOCKS5_CONN_MSG_LEN];
511 : : size_t len;
512 : :
513 : 0 : if (!set_connect_msg (msg, hostname, port, &len, error))
514 : 0 : goto error;
515 : :
516 : 0 : if (!g_output_stream_write_all (out, msg, len, NULL,
517 : : cancellable, error))
518 : 0 : goto error;
519 : : }
520 : :
521 : : /* Read SOCKS5 response */
522 : : {
523 : : guint8 data[SOCKS5_CONN_REP_LEN];
524 : : gint atype;
525 : :
526 : 0 : if (!g_input_stream_read_all (in, data, 4 /* VER, REP, RSV, ATYP */, NULL,
527 : : cancellable, error))
528 : 0 : goto error;
529 : :
530 : 0 : if (!parse_connect_reply (data, &atype, error))
531 : 0 : goto error;
532 : :
533 : 0 : switch (atype)
534 : : {
535 : 0 : case SOCKS5_ATYP_IPV4:
536 : 0 : if (!g_input_stream_read_all (in, data,
537 : : 4 /* IPv4 length */ + 2 /* port */,
538 : : NULL, cancellable, error))
539 : 0 : goto error;
540 : 0 : break;
541 : :
542 : 0 : case SOCKS5_ATYP_IPV6:
543 : 0 : if (!g_input_stream_read_all (in, data,
544 : : 16 /* IPv6 length */ + 2 /* port */,
545 : : NULL, cancellable, error))
546 : 0 : goto error;
547 : 0 : break;
548 : :
549 : 0 : case SOCKS5_ATYP_DOMAINNAME:
550 : 0 : if (!g_input_stream_read_all (in, data, 1 /* domain name length */,
551 : : NULL, cancellable, error))
552 : 0 : goto error;
553 : 0 : if (!g_input_stream_read_all (in, data,
554 : 0 : data[0] /* domain name length */ + 2 /* port */,
555 : : NULL, cancellable, error))
556 : 0 : goto error;
557 : 0 : break;
558 : : }
559 : : }
560 : :
561 : 0 : return g_object_ref (io_stream);
562 : :
563 : 0 : error:
564 : 0 : return NULL;
565 : : }
566 : :
567 : :
568 : : typedef struct
569 : : {
570 : : GIOStream *io_stream;
571 : : gchar *hostname;
572 : : guint16 port;
573 : : gchar *username;
574 : : gchar *password;
575 : : guint8 *buffer;
576 : : size_t length;
577 : : size_t offset;
578 : : } ConnectAsyncData;
579 : :
580 : : static void nego_msg_write_cb (GObject *source,
581 : : GAsyncResult *res,
582 : : gpointer user_data);
583 : : static void nego_reply_read_cb (GObject *source,
584 : : GAsyncResult *res,
585 : : gpointer user_data);
586 : : static void auth_msg_write_cb (GObject *source,
587 : : GAsyncResult *res,
588 : : gpointer user_data);
589 : : static void auth_reply_read_cb (GObject *source,
590 : : GAsyncResult *result,
591 : : gpointer user_data);
592 : : static void send_connect_msg (GTask *task);
593 : : static void connect_msg_write_cb (GObject *source,
594 : : GAsyncResult *result,
595 : : gpointer user_data);
596 : : static void connect_reply_read_cb (GObject *source,
597 : : GAsyncResult *result,
598 : : gpointer user_data);
599 : : static void connect_addr_len_read_cb (GObject *source,
600 : : GAsyncResult *result,
601 : : gpointer user_data);
602 : : static void connect_addr_read_cb (GObject *source,
603 : : GAsyncResult *result,
604 : : gpointer user_data);
605 : :
606 : : static void
607 : 0 : free_connect_data (ConnectAsyncData *data)
608 : : {
609 : 0 : g_object_unref (data->io_stream);
610 : :
611 : 0 : g_free (data->hostname);
612 : 0 : g_free (data->username);
613 : 0 : g_free (data->password);
614 : 0 : g_free (data->buffer);
615 : :
616 : 0 : g_slice_free (ConnectAsyncData, data);
617 : 0 : }
618 : :
619 : : static void
620 : 0 : do_read (GAsyncReadyCallback callback, GTask *task, ConnectAsyncData *data)
621 : : {
622 : : GInputStream *in;
623 : 0 : in = g_io_stream_get_input_stream (data->io_stream);
624 : 0 : g_input_stream_read_async (in,
625 : 0 : data->buffer + data->offset,
626 : 0 : data->length - data->offset,
627 : : g_task_get_priority (task),
628 : : g_task_get_cancellable (task),
629 : : callback, task);
630 : 0 : }
631 : :
632 : : static void
633 : 0 : do_write (GAsyncReadyCallback callback, GTask *task, ConnectAsyncData *data)
634 : : {
635 : : GOutputStream *out;
636 : 0 : out = g_io_stream_get_output_stream (data->io_stream);
637 : 0 : g_output_stream_write_async (out,
638 : 0 : data->buffer + data->offset,
639 : 0 : data->length - data->offset,
640 : : g_task_get_priority (task),
641 : : g_task_get_cancellable (task),
642 : : callback, task);
643 : 0 : }
644 : :
645 : : static void
646 : 0 : g_socks5_proxy_connect_async (GProxy *proxy,
647 : : GIOStream *io_stream,
648 : : GProxyAddress *proxy_address,
649 : : GCancellable *cancellable,
650 : : GAsyncReadyCallback callback,
651 : : gpointer user_data)
652 : : {
653 : : GTask *task;
654 : : ConnectAsyncData *data;
655 : :
656 : 0 : data = g_slice_new0 (ConnectAsyncData);
657 : 0 : data->io_stream = g_object_ref (io_stream);
658 : :
659 : 0 : task = g_task_new (proxy, cancellable, callback, user_data);
660 : 0 : g_task_set_source_tag (task, g_socks5_proxy_connect_async);
661 : 0 : g_task_set_task_data (task, data, (GDestroyNotify) free_connect_data);
662 : :
663 : 0 : g_object_get (G_OBJECT (proxy_address),
664 : : "destination-hostname", &data->hostname,
665 : : "destination-port", &data->port,
666 : : "username", &data->username,
667 : : "password", &data->password,
668 : : NULL);
669 : :
670 : 0 : data->buffer = g_malloc0 (SOCKS5_NEGO_MSG_LEN);
671 : 0 : data->length = set_nego_msg (data->buffer,
672 : 0 : data->username || data->password);
673 : 0 : data->offset = 0;
674 : :
675 : 0 : do_write (nego_msg_write_cb, task, data);
676 : 0 : }
677 : :
678 : :
679 : : static void
680 : 0 : nego_msg_write_cb (GObject *source,
681 : : GAsyncResult *res,
682 : : gpointer user_data)
683 : : {
684 : 0 : GTask *task = user_data;
685 : 0 : ConnectAsyncData *data = g_task_get_task_data (task);
686 : 0 : GError *error = NULL;
687 : : gssize written;
688 : :
689 : 0 : written = g_output_stream_write_finish (G_OUTPUT_STREAM (source),
690 : : res, &error);
691 : :
692 : 0 : if (written < 0)
693 : : {
694 : 0 : g_task_return_error (task, error);
695 : 0 : g_object_unref (task);
696 : 0 : return;
697 : : }
698 : :
699 : 0 : data->offset += written;
700 : :
701 : 0 : if (data->offset == data->length)
702 : : {
703 : 0 : g_free (data->buffer);
704 : :
705 : 0 : data->buffer = g_malloc0 (SOCKS5_NEGO_REP_LEN);
706 : 0 : data->length = SOCKS5_NEGO_REP_LEN;
707 : 0 : data->offset = 0;
708 : :
709 : 0 : do_read (nego_reply_read_cb, task, data);
710 : : }
711 : : else
712 : : {
713 : 0 : do_write (nego_msg_write_cb, task, data);
714 : : }
715 : : }
716 : :
717 : : static void
718 : 0 : nego_reply_read_cb (GObject *source,
719 : : GAsyncResult *res,
720 : : gpointer user_data)
721 : : {
722 : 0 : GTask *task = user_data;
723 : 0 : ConnectAsyncData *data = g_task_get_task_data (task);
724 : 0 : GError *error = NULL;
725 : : gssize read;
726 : :
727 : 0 : read = g_input_stream_read_finish (G_INPUT_STREAM (source),
728 : : res, &error);
729 : :
730 : 0 : if (read < 0)
731 : : {
732 : 0 : g_task_return_error (task, error);
733 : 0 : g_object_unref (task);
734 : 0 : return;
735 : : }
736 : :
737 : 0 : if (read == 0)
738 : : {
739 : 0 : g_task_return_new_error_literal (task,
740 : : G_IO_ERROR,
741 : : G_IO_ERROR_CONNECTION_CLOSED,
742 : : "Connection to SOCKSv5 proxy server lost");
743 : 0 : g_object_unref (task);
744 : 0 : return;
745 : : }
746 : :
747 : 0 : data->offset += read;
748 : :
749 : 0 : if (data->offset == data->length)
750 : : {
751 : 0 : gboolean must_auth = FALSE;
752 : 0 : gboolean has_auth = data->username || data->password;
753 : :
754 : 0 : if (!parse_nego_reply (data->buffer, has_auth, &must_auth, &error))
755 : : {
756 : 0 : g_task_return_error (task, error);
757 : 0 : g_object_unref (task);
758 : 0 : return;
759 : : }
760 : :
761 : 0 : if (must_auth)
762 : : {
763 : 0 : g_free (data->buffer);
764 : :
765 : 0 : data->buffer = g_malloc0 (SOCKS5_AUTH_MSG_LEN);
766 : 0 : data->offset = 0;
767 : :
768 : 0 : if (!set_auth_msg (data->buffer,
769 : 0 : data->username,
770 : 0 : data->password,
771 : : &data->length,
772 : : &error))
773 : : {
774 : 0 : g_task_return_error (task, error);
775 : 0 : g_object_unref (task);
776 : 0 : return;
777 : : }
778 : :
779 : 0 : do_write (auth_msg_write_cb, task, data);
780 : : }
781 : : else
782 : : {
783 : 0 : send_connect_msg (task);
784 : : }
785 : : }
786 : : else
787 : : {
788 : 0 : do_read (nego_reply_read_cb, task, data);
789 : : }
790 : : }
791 : :
792 : : static void
793 : 0 : auth_msg_write_cb (GObject *source,
794 : : GAsyncResult *result,
795 : : gpointer user_data)
796 : : {
797 : 0 : GTask *task = user_data;
798 : 0 : ConnectAsyncData *data = g_task_get_task_data (task);
799 : 0 : GError *error = NULL;
800 : : gssize written;
801 : :
802 : 0 : written = g_output_stream_write_finish (G_OUTPUT_STREAM (source),
803 : : result, &error);
804 : :
805 : 0 : if (written < 0)
806 : : {
807 : 0 : g_task_return_error (task, error);
808 : 0 : g_object_unref (task);
809 : 0 : return;
810 : : }
811 : :
812 : 0 : data->offset += written;
813 : :
814 : 0 : if (data->offset == data->length)
815 : : {
816 : 0 : g_free (data->buffer);
817 : :
818 : 0 : data->buffer = g_malloc0 (SOCKS5_NEGO_REP_LEN);
819 : 0 : data->length = SOCKS5_NEGO_REP_LEN;
820 : 0 : data->offset = 0;
821 : :
822 : 0 : do_read (auth_reply_read_cb, task, data);
823 : : }
824 : : else
825 : : {
826 : 0 : do_write (auth_msg_write_cb, task, data);
827 : : }
828 : : }
829 : :
830 : : static void
831 : 0 : auth_reply_read_cb (GObject *source,
832 : : GAsyncResult *result,
833 : : gpointer user_data)
834 : : {
835 : 0 : GTask *task = user_data;
836 : 0 : ConnectAsyncData *data = g_task_get_task_data (task);
837 : 0 : GError *error = NULL;
838 : : gssize read;
839 : :
840 : 0 : read = g_input_stream_read_finish (G_INPUT_STREAM (source),
841 : : result, &error);
842 : :
843 : 0 : if (read < 0)
844 : : {
845 : 0 : g_task_return_error (task, error);
846 : 0 : g_object_unref (task);
847 : 0 : return;
848 : : }
849 : :
850 : 0 : if (read == 0)
851 : : {
852 : 0 : g_task_return_new_error_literal (task,
853 : : G_IO_ERROR,
854 : : G_IO_ERROR_CONNECTION_CLOSED,
855 : : "Connection to SOCKSv5 proxy server lost");
856 : 0 : g_object_unref (task);
857 : 0 : return;
858 : : }
859 : :
860 : 0 : data->offset += read;
861 : :
862 : 0 : if (data->offset == data->length)
863 : : {
864 : 0 : if (!check_auth_status (data->buffer, &error))
865 : : {
866 : 0 : g_task_return_error (task, error);
867 : 0 : g_object_unref (task);
868 : 0 : return;
869 : : }
870 : :
871 : 0 : send_connect_msg (task);
872 : : }
873 : : else
874 : : {
875 : 0 : do_read (auth_reply_read_cb, task, data);
876 : : }
877 : : }
878 : :
879 : : static void
880 : 0 : send_connect_msg (GTask *task)
881 : : {
882 : 0 : ConnectAsyncData *data = g_task_get_task_data (task);
883 : 0 : GError *error = NULL;
884 : :
885 : 0 : g_free (data->buffer);
886 : :
887 : 0 : data->buffer = g_malloc0 (SOCKS5_CONN_MSG_LEN);
888 : 0 : data->offset = 0;
889 : :
890 : 0 : if (!set_connect_msg (data->buffer,
891 : 0 : data->hostname,
892 : 0 : data->port,
893 : : &data->length,
894 : : &error))
895 : : {
896 : 0 : g_task_return_error (task, error);
897 : 0 : g_object_unref (task);
898 : 0 : return;
899 : : }
900 : :
901 : 0 : do_write (connect_msg_write_cb, task, data);
902 : : }
903 : :
904 : : static void
905 : 0 : connect_msg_write_cb (GObject *source,
906 : : GAsyncResult *result,
907 : : gpointer user_data)
908 : : {
909 : 0 : GTask *task = user_data;
910 : 0 : ConnectAsyncData *data = g_task_get_task_data (task);
911 : 0 : GError *error = NULL;
912 : : gssize written;
913 : :
914 : 0 : written = g_output_stream_write_finish (G_OUTPUT_STREAM (source),
915 : : result, &error);
916 : :
917 : 0 : if (written < 0)
918 : : {
919 : 0 : g_task_return_error (task, error);
920 : 0 : g_object_unref (task);
921 : 0 : return;
922 : : }
923 : :
924 : 0 : data->offset += written;
925 : :
926 : 0 : if (data->offset == data->length)
927 : : {
928 : 0 : g_free (data->buffer);
929 : :
930 : 0 : data->buffer = g_malloc0 (SOCKS5_CONN_REP_LEN);
931 : 0 : data->length = 4;
932 : 0 : data->offset = 0;
933 : :
934 : 0 : do_read (connect_reply_read_cb, task, data);
935 : : }
936 : : else
937 : : {
938 : 0 : do_write (connect_msg_write_cb, task, data);
939 : : }
940 : : }
941 : :
942 : : static void
943 : 0 : connect_reply_read_cb (GObject *source,
944 : : GAsyncResult *result,
945 : : gpointer user_data)
946 : : {
947 : 0 : GTask *task = user_data;
948 : 0 : ConnectAsyncData *data = g_task_get_task_data (task);
949 : 0 : GError *error = NULL;
950 : : gssize read;
951 : :
952 : 0 : read = g_input_stream_read_finish (G_INPUT_STREAM (source),
953 : : result, &error);
954 : :
955 : 0 : if (read < 0)
956 : : {
957 : 0 : g_task_return_error (task, error);
958 : 0 : g_object_unref (task);
959 : 0 : return;
960 : : }
961 : :
962 : 0 : if (read == 0)
963 : : {
964 : 0 : g_task_return_new_error_literal (task,
965 : : G_IO_ERROR,
966 : : G_IO_ERROR_CONNECTION_CLOSED,
967 : : "Connection to SOCKSv5 proxy server lost");
968 : 0 : g_object_unref (task);
969 : 0 : return;
970 : : }
971 : :
972 : 0 : data->offset += read;
973 : :
974 : 0 : if (data->offset == data->length)
975 : : {
976 : : gint atype;
977 : :
978 : 0 : if (!parse_connect_reply (data->buffer, &atype, &error))
979 : : {
980 : 0 : g_task_return_error (task, error);
981 : 0 : g_object_unref (task);
982 : 0 : return;
983 : : }
984 : :
985 : 0 : switch (atype)
986 : : {
987 : 0 : case SOCKS5_ATYP_IPV4:
988 : 0 : data->length = 6;
989 : 0 : data->offset = 0;
990 : 0 : do_read (connect_addr_read_cb, task, data);
991 : 0 : break;
992 : :
993 : 0 : case SOCKS5_ATYP_IPV6:
994 : 0 : data->length = 18;
995 : 0 : data->offset = 0;
996 : 0 : do_read (connect_addr_read_cb, task, data);
997 : 0 : break;
998 : :
999 : 0 : case SOCKS5_ATYP_DOMAINNAME:
1000 : 0 : data->length = 1;
1001 : 0 : data->offset = 0;
1002 : 0 : do_read (connect_addr_len_read_cb, task, data);
1003 : 0 : break;
1004 : : }
1005 : : }
1006 : : else
1007 : : {
1008 : 0 : do_read (connect_reply_read_cb, task, data);
1009 : : }
1010 : : }
1011 : :
1012 : : static void
1013 : 0 : connect_addr_len_read_cb (GObject *source,
1014 : : GAsyncResult *result,
1015 : : gpointer user_data)
1016 : : {
1017 : 0 : GTask *task = user_data;
1018 : 0 : ConnectAsyncData *data = g_task_get_task_data (task);
1019 : 0 : GError *error = NULL;
1020 : : gssize read;
1021 : :
1022 : 0 : read = g_input_stream_read_finish (G_INPUT_STREAM (source),
1023 : : result, &error);
1024 : :
1025 : 0 : if (read < 0)
1026 : : {
1027 : 0 : g_task_return_error (task, error);
1028 : 0 : g_object_unref (task);
1029 : 0 : return;
1030 : : }
1031 : :
1032 : 0 : if (read == 0)
1033 : : {
1034 : 0 : g_task_return_new_error_literal (task,
1035 : : G_IO_ERROR,
1036 : : G_IO_ERROR_CONNECTION_CLOSED,
1037 : : "Connection to SOCKSv5 proxy server lost");
1038 : 0 : g_object_unref (task);
1039 : 0 : return;
1040 : : }
1041 : :
1042 : 0 : data->length = data->buffer[0] + 2;
1043 : 0 : data->offset = 0;
1044 : :
1045 : 0 : do_read (connect_addr_read_cb, task, data);
1046 : : }
1047 : :
1048 : : static void
1049 : 0 : connect_addr_read_cb (GObject *source,
1050 : : GAsyncResult *result,
1051 : : gpointer user_data)
1052 : : {
1053 : 0 : GTask *task = user_data;
1054 : 0 : ConnectAsyncData *data = g_task_get_task_data (task);
1055 : 0 : GError *error = NULL;
1056 : : gssize read;
1057 : :
1058 : 0 : read = g_input_stream_read_finish (G_INPUT_STREAM (source),
1059 : : result, &error);
1060 : :
1061 : 0 : if (read < 0)
1062 : : {
1063 : 0 : g_task_return_error (task, error);
1064 : 0 : g_object_unref (task);
1065 : 0 : return;
1066 : : }
1067 : :
1068 : 0 : if (read == 0)
1069 : : {
1070 : 0 : g_task_return_new_error_literal (task,
1071 : : G_IO_ERROR,
1072 : : G_IO_ERROR_CONNECTION_CLOSED,
1073 : : "Connection to SOCKSv5 proxy server lost");
1074 : 0 : g_object_unref (task);
1075 : 0 : return;
1076 : : }
1077 : :
1078 : 0 : data->offset += read;
1079 : :
1080 : 0 : if (data->offset == data->length)
1081 : : {
1082 : 0 : g_task_return_pointer (task, g_object_ref (data->io_stream), g_object_unref);
1083 : 0 : g_object_unref (task);
1084 : 0 : return;
1085 : : }
1086 : : else
1087 : : {
1088 : 0 : do_read (connect_reply_read_cb, task, data);
1089 : : }
1090 : : }
1091 : :
1092 : : static GIOStream *
1093 : 0 : g_socks5_proxy_connect_finish (GProxy *proxy,
1094 : : GAsyncResult *result,
1095 : : GError **error)
1096 : : {
1097 : 0 : return g_task_propagate_pointer (G_TASK (result), error);
1098 : : }
1099 : :
1100 : : static gboolean
1101 : 0 : g_socks5_proxy_supports_hostname (GProxy *proxy)
1102 : : {
1103 : 0 : return TRUE;
1104 : : }
1105 : :
1106 : : static void
1107 : 0 : g_socks5_proxy_class_init (GSocks5ProxyClass *class)
1108 : : {
1109 : : GObjectClass *object_class;
1110 : :
1111 : 0 : object_class = (GObjectClass *) class;
1112 : 0 : object_class->finalize = g_socks5_proxy_finalize;
1113 : 0 : }
1114 : :
1115 : : static void
1116 : 0 : g_socks5_proxy_iface_init (GProxyInterface *proxy_iface)
1117 : : {
1118 : 0 : proxy_iface->connect = g_socks5_proxy_connect;
1119 : 0 : proxy_iface->connect_async = g_socks5_proxy_connect_async;
1120 : 0 : proxy_iface->connect_finish = g_socks5_proxy_connect_finish;
1121 : 0 : proxy_iface->supports_hostname = g_socks5_proxy_supports_hostname;
1122 : 0 : }
|