/src/glib/gio/gsocks4aproxy.c
Line | Count | Source |
1 | | /* GIO - GLib Input, Output and Streaming Library |
2 | | * |
3 | | * Copyright (C) 2010 Collabora, Ltd. |
4 | | * |
5 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
6 | | * |
7 | | * This library is free software; you can redistribute it and/or |
8 | | * modify it under the terms of the GNU Lesser General Public |
9 | | * License as published by the Free Software Foundation; either |
10 | | * version 2.1 of the License, or (at your option) any later version. |
11 | | * |
12 | | * This library is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | | * Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General |
18 | | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
19 | | * |
20 | | * Author: Nicolas Dufresne <nicolas.dufresne@collabora.co.uk> |
21 | | */ |
22 | | |
23 | | #include "config.h" |
24 | | |
25 | | #include "gsocks4aproxy.h" |
26 | | |
27 | | #include <string.h> |
28 | | |
29 | | #include "giomodule.h" |
30 | | #include "giomodule-priv.h" |
31 | | #include "giostream.h" |
32 | | #include "ginetaddress.h" |
33 | | #include "ginputstream.h" |
34 | | #include "glibintl.h" |
35 | | #include "goutputstream.h" |
36 | | #include "gproxy.h" |
37 | | #include "gproxyaddress.h" |
38 | | #include "gtask.h" |
39 | | |
40 | 0 | #define SOCKS4_VERSION 4 |
41 | | |
42 | 0 | #define SOCKS4_CMD_CONNECT 1 |
43 | | #define SOCKS4_CMD_BIND 2 |
44 | | |
45 | 0 | #define SOCKS4_MAX_LEN 255 |
46 | | |
47 | 0 | #define SOCKS4_REP_VERSION 0 |
48 | 0 | #define SOCKS4_REP_GRANTED 90 |
49 | | #define SOCKS4_REP_REJECTED 91 |
50 | | #define SOCKS4_REP_NO_IDENT 92 |
51 | | #define SOCKS4_REP_BAD_IDENT 93 |
52 | | |
53 | | static void g_socks4a_proxy_iface_init (GProxyInterface *proxy_iface); |
54 | | |
55 | | #define g_socks4a_proxy_get_type _g_socks4a_proxy_get_type |
56 | 0 | G_DEFINE_TYPE_WITH_CODE (GSocks4aProxy, g_socks4a_proxy, G_TYPE_OBJECT, |
57 | 0 | G_IMPLEMENT_INTERFACE (G_TYPE_PROXY, |
58 | 0 | g_socks4a_proxy_iface_init) |
59 | 0 | _g_io_modules_ensure_extension_points_registered (); |
60 | 0 | g_io_extension_point_implement (G_PROXY_EXTENSION_POINT_NAME, |
61 | 0 | g_define_type_id, |
62 | 0 | "socks4a", |
63 | 0 | 0)) |
64 | 0 |
|
65 | 0 | static void |
66 | 0 | g_socks4a_proxy_finalize (GObject *object) |
67 | 0 | { |
68 | | /* must chain up */ |
69 | 0 | G_OBJECT_CLASS (g_socks4a_proxy_parent_class)->finalize (object); |
70 | 0 | } |
71 | | |
72 | | static void |
73 | | g_socks4a_proxy_init (GSocks4aProxy *proxy) |
74 | 0 | { |
75 | 0 | proxy->supports_hostname = TRUE; |
76 | 0 | } |
77 | | |
78 | | /* |-> SOCKSv4a only |
79 | | * +----+----+----+----+----+----+----+----+----+----+....+----+------+....+------+ |
80 | | * | VN | CD | DSTPORT | DSTIP | USERID |NULL| HOST | | NULL | |
81 | | * +----+----+----+----+----+----+----+----+----+----+....+----+------+....+------+ |
82 | | * 1 1 2 4 variable 1 variable 1 |
83 | | */ |
84 | 0 | #define SOCKS4_CONN_MSG_LEN (10 + SOCKS4_MAX_LEN * 2) |
85 | | static gint |
86 | | set_connect_msg (guint8 *msg, |
87 | | const gchar *hostname, |
88 | | guint16 port, |
89 | | const char *username, |
90 | | GError **error) |
91 | 0 | { |
92 | 0 | GInetAddress *addr; |
93 | 0 | guint len = 0; |
94 | 0 | gsize addr_len; |
95 | 0 | gboolean is_ip; |
96 | 0 | const gchar *ip; |
97 | |
|
98 | 0 | msg[len++] = SOCKS4_VERSION; |
99 | 0 | msg[len++] = SOCKS4_CMD_CONNECT; |
100 | |
|
101 | 0 | { |
102 | 0 | guint16 hp = g_htons (port); |
103 | 0 | memcpy (msg + len, &hp, 2); |
104 | 0 | len += 2; |
105 | 0 | } |
106 | |
|
107 | 0 | is_ip = g_hostname_is_ip_address (hostname); |
108 | |
|
109 | 0 | if (is_ip) |
110 | 0 | ip = hostname; |
111 | 0 | else |
112 | 0 | ip = "0.0.0.1"; |
113 | | |
114 | 0 | addr = g_inet_address_new_from_string (ip); |
115 | 0 | addr_len = g_inet_address_get_native_size (addr); |
116 | |
|
117 | 0 | if (addr_len != 4) |
118 | 0 | { |
119 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, |
120 | 0 | _("SOCKSv4 does not support IPv6 address ā%sā"), |
121 | 0 | ip); |
122 | 0 | g_object_unref (addr); |
123 | 0 | return -1; |
124 | 0 | } |
125 | | |
126 | 0 | memcpy (msg + len, g_inet_address_to_bytes (addr), addr_len); |
127 | 0 | len += addr_len; |
128 | |
|
129 | 0 | g_object_unref (addr); |
130 | |
|
131 | 0 | if (username) |
132 | 0 | { |
133 | 0 | gsize user_len = strlen (username); |
134 | |
|
135 | 0 | if (user_len > SOCKS4_MAX_LEN) |
136 | 0 | { |
137 | 0 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, |
138 | 0 | _("Username is too long for SOCKSv4 protocol")); |
139 | 0 | return -1; |
140 | 0 | } |
141 | | |
142 | 0 | memcpy (msg + len, username, user_len); |
143 | 0 | len += user_len; |
144 | 0 | } |
145 | | |
146 | 0 | msg[len++] = '\0'; |
147 | |
|
148 | 0 | if (!is_ip) |
149 | 0 | { |
150 | 0 | gsize host_len = strlen (hostname); |
151 | |
|
152 | 0 | if (host_len > SOCKS4_MAX_LEN) |
153 | 0 | { |
154 | 0 | g_set_error (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, |
155 | 0 | _("Hostname ā%sā is too long for SOCKSv4 protocol"), |
156 | 0 | hostname); |
157 | 0 | return -1; |
158 | 0 | } |
159 | | |
160 | 0 | memcpy (msg + len, hostname, host_len); |
161 | 0 | len += host_len; |
162 | 0 | msg[len++] = '\0'; |
163 | 0 | } |
164 | | |
165 | 0 | return len; |
166 | 0 | } |
167 | | |
168 | | /* |
169 | | * +----+----+----+----+----+----+----+----+ |
170 | | * | VN | CD | DSTPORT | DSTIP | |
171 | | * +----+----+----+----+----+----+----+----+ |
172 | | * 1 1 2 4 |
173 | | */ |
174 | 0 | #define SOCKS4_CONN_REP_LEN 8 |
175 | | static gboolean |
176 | | parse_connect_reply (const guint8 *data, GError **error) |
177 | 0 | { |
178 | 0 | if (data[0] != SOCKS4_REP_VERSION) |
179 | 0 | { |
180 | 0 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, |
181 | 0 | _("The server is not a SOCKSv4 proxy server.")); |
182 | 0 | return FALSE; |
183 | 0 | } |
184 | | |
185 | 0 | if (data[1] != SOCKS4_REP_GRANTED) |
186 | 0 | { |
187 | 0 | g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_PROXY_FAILED, |
188 | 0 | _("Connection through SOCKSv4 server was rejected")); |
189 | 0 | return FALSE; |
190 | 0 | } |
191 | | |
192 | 0 | return TRUE; |
193 | 0 | } |
194 | | |
195 | | static GIOStream * |
196 | | g_socks4a_proxy_connect (GProxy *proxy, |
197 | | GIOStream *io_stream, |
198 | | GProxyAddress *proxy_address, |
199 | | GCancellable *cancellable, |
200 | | GError **error) |
201 | 0 | { |
202 | 0 | GInputStream *in; |
203 | 0 | GOutputStream *out; |
204 | 0 | const gchar *hostname; |
205 | 0 | guint16 port; |
206 | 0 | const gchar *username; |
207 | |
|
208 | 0 | hostname = g_proxy_address_get_destination_hostname (proxy_address); |
209 | 0 | port = g_proxy_address_get_destination_port (proxy_address); |
210 | 0 | username = g_proxy_address_get_username (proxy_address); |
211 | |
|
212 | 0 | in = g_io_stream_get_input_stream (io_stream); |
213 | 0 | out = g_io_stream_get_output_stream (io_stream); |
214 | | |
215 | | /* Send SOCKS4 connection request */ |
216 | 0 | { |
217 | 0 | guint8 msg[SOCKS4_CONN_MSG_LEN]; |
218 | 0 | gint len; |
219 | | |
220 | 0 | len = set_connect_msg (msg, hostname, port, username, error); |
221 | |
|
222 | 0 | if (len < 0) |
223 | 0 | goto error; |
224 | | |
225 | 0 | if (!g_output_stream_write_all (out, msg, len, NULL, |
226 | 0 | cancellable, error)) |
227 | 0 | goto error; |
228 | 0 | } |
229 | | |
230 | | /* Read SOCKS4 response */ |
231 | 0 | { |
232 | 0 | guint8 data[SOCKS4_CONN_REP_LEN]; |
233 | |
|
234 | 0 | if (!g_input_stream_read_all (in, data, SOCKS4_CONN_REP_LEN, NULL, |
235 | 0 | cancellable, error)) |
236 | 0 | goto error; |
237 | | |
238 | 0 | if (!parse_connect_reply (data, error)) |
239 | 0 | goto error; |
240 | 0 | } |
241 | | |
242 | 0 | return g_object_ref (io_stream); |
243 | | |
244 | 0 | error: |
245 | 0 | return NULL; |
246 | 0 | } |
247 | | |
248 | | |
249 | | typedef struct |
250 | | { |
251 | | GIOStream *io_stream; |
252 | | |
253 | | /* For connecting */ |
254 | | guint8 *buffer; |
255 | | gssize length; |
256 | | gssize offset; |
257 | | |
258 | | } ConnectAsyncData; |
259 | | |
260 | | static void connect_msg_write_cb (GObject *source, |
261 | | GAsyncResult *result, |
262 | | gpointer user_data); |
263 | | static void connect_reply_read_cb (GObject *source, |
264 | | GAsyncResult *result, |
265 | | gpointer user_data); |
266 | | |
267 | | static void |
268 | | free_connect_data (ConnectAsyncData *data) |
269 | 0 | { |
270 | 0 | g_object_unref (data->io_stream); |
271 | 0 | g_free (data->buffer); |
272 | 0 | g_slice_free (ConnectAsyncData, data); |
273 | 0 | } |
274 | | |
275 | | static void |
276 | | do_read (GAsyncReadyCallback callback, GTask *task, ConnectAsyncData *data) |
277 | 0 | { |
278 | 0 | GInputStream *in; |
279 | 0 | in = g_io_stream_get_input_stream (data->io_stream); |
280 | 0 | g_input_stream_read_async (in, |
281 | 0 | data->buffer + data->offset, |
282 | 0 | data->length - data->offset, |
283 | 0 | g_task_get_priority (task), |
284 | 0 | g_task_get_cancellable (task), |
285 | 0 | callback, task); |
286 | 0 | } |
287 | | |
288 | | static void |
289 | | do_write (GAsyncReadyCallback callback, GTask *task, ConnectAsyncData *data) |
290 | 0 | { |
291 | 0 | GOutputStream *out; |
292 | 0 | out = g_io_stream_get_output_stream (data->io_stream); |
293 | 0 | g_output_stream_write_async (out, |
294 | 0 | data->buffer + data->offset, |
295 | 0 | data->length - data->offset, |
296 | 0 | g_task_get_priority (task), |
297 | 0 | g_task_get_cancellable (task), |
298 | 0 | callback, task); |
299 | 0 | } |
300 | | |
301 | | |
302 | | |
303 | | static void |
304 | | g_socks4a_proxy_connect_async (GProxy *proxy, |
305 | | GIOStream *io_stream, |
306 | | GProxyAddress *proxy_address, |
307 | | GCancellable *cancellable, |
308 | | GAsyncReadyCallback callback, |
309 | | gpointer user_data) |
310 | 0 | { |
311 | 0 | GError *error = NULL; |
312 | 0 | GTask *task; |
313 | 0 | ConnectAsyncData *data; |
314 | 0 | const gchar *hostname; |
315 | 0 | guint16 port; |
316 | 0 | const gchar *username; |
317 | |
|
318 | 0 | data = g_slice_new0 (ConnectAsyncData); |
319 | 0 | data->io_stream = g_object_ref (io_stream); |
320 | |
|
321 | 0 | task = g_task_new (proxy, cancellable, callback, user_data); |
322 | 0 | g_task_set_source_tag (task, g_socks4a_proxy_connect_async); |
323 | 0 | g_task_set_task_data (task, data, (GDestroyNotify) free_connect_data); |
324 | |
|
325 | 0 | hostname = g_proxy_address_get_destination_hostname (proxy_address); |
326 | 0 | port = g_proxy_address_get_destination_port (proxy_address); |
327 | 0 | username = g_proxy_address_get_username (proxy_address); |
328 | |
|
329 | 0 | data->buffer = g_malloc0 (SOCKS4_CONN_MSG_LEN); |
330 | 0 | data->length = set_connect_msg (data->buffer, |
331 | 0 | hostname, port, username, |
332 | 0 | &error); |
333 | 0 | data->offset = 0; |
334 | |
|
335 | 0 | if (data->length < 0) |
336 | 0 | { |
337 | 0 | g_task_return_error (task, error); |
338 | 0 | g_object_unref (task); |
339 | 0 | } |
340 | 0 | else |
341 | 0 | { |
342 | 0 | do_write (connect_msg_write_cb, task, data); |
343 | 0 | } |
344 | 0 | } |
345 | | |
346 | | static void |
347 | | connect_msg_write_cb (GObject *source, |
348 | | GAsyncResult *result, |
349 | | gpointer user_data) |
350 | 0 | { |
351 | 0 | GTask *task = user_data; |
352 | 0 | ConnectAsyncData *data = g_task_get_task_data (task); |
353 | 0 | GError *error = NULL; |
354 | 0 | gssize written; |
355 | |
|
356 | 0 | written = g_output_stream_write_finish (G_OUTPUT_STREAM (source), |
357 | 0 | result, &error); |
358 | | |
359 | 0 | if (written < 0) |
360 | 0 | { |
361 | 0 | g_task_return_error (task, error); |
362 | 0 | g_object_unref (task); |
363 | 0 | return; |
364 | 0 | } |
365 | | |
366 | 0 | data->offset += written; |
367 | |
|
368 | 0 | if (data->offset == data->length) |
369 | 0 | { |
370 | 0 | g_free (data->buffer); |
371 | |
|
372 | 0 | data->buffer = g_malloc0 (SOCKS4_CONN_REP_LEN); |
373 | 0 | data->length = SOCKS4_CONN_REP_LEN; |
374 | 0 | data->offset = 0; |
375 | |
|
376 | 0 | do_read (connect_reply_read_cb, task, data); |
377 | 0 | } |
378 | 0 | else |
379 | 0 | { |
380 | 0 | do_write (connect_msg_write_cb, task, data); |
381 | 0 | } |
382 | 0 | } |
383 | | |
384 | | static void |
385 | | connect_reply_read_cb (GObject *source, |
386 | | GAsyncResult *result, |
387 | | gpointer user_data) |
388 | 0 | { |
389 | 0 | GTask *task = user_data; |
390 | 0 | ConnectAsyncData *data = g_task_get_task_data (task); |
391 | 0 | GError *error = NULL; |
392 | 0 | gssize read; |
393 | |
|
394 | 0 | read = g_input_stream_read_finish (G_INPUT_STREAM (source), |
395 | 0 | result, &error); |
396 | |
|
397 | 0 | if (read < 0) |
398 | 0 | { |
399 | 0 | g_task_return_error (task, error); |
400 | 0 | g_object_unref (task); |
401 | 0 | return; |
402 | 0 | } |
403 | | |
404 | 0 | data->offset += read; |
405 | |
|
406 | 0 | if (data->offset == data->length) |
407 | 0 | { |
408 | 0 | if (!parse_connect_reply (data->buffer, &error)) |
409 | 0 | { |
410 | 0 | g_task_return_error (task, error); |
411 | 0 | g_object_unref (task); |
412 | 0 | return; |
413 | 0 | } |
414 | 0 | else |
415 | 0 | { |
416 | 0 | g_task_return_pointer (task, g_object_ref (data->io_stream), g_object_unref); |
417 | 0 | g_object_unref (task); |
418 | 0 | return; |
419 | 0 | } |
420 | 0 | } |
421 | 0 | else |
422 | 0 | { |
423 | 0 | do_read (connect_reply_read_cb, task, data); |
424 | 0 | } |
425 | 0 | } |
426 | | |
427 | | static GIOStream *g_socks4a_proxy_connect_finish (GProxy *proxy, |
428 | | GAsyncResult *result, |
429 | | GError **error); |
430 | | |
431 | | static GIOStream * |
432 | | g_socks4a_proxy_connect_finish (GProxy *proxy, |
433 | | GAsyncResult *result, |
434 | | GError **error) |
435 | 0 | { |
436 | 0 | return g_task_propagate_pointer (G_TASK (result), error); |
437 | 0 | } |
438 | | |
439 | | static gboolean |
440 | | g_socks4a_proxy_supports_hostname (GProxy *proxy) |
441 | 0 | { |
442 | 0 | return G_SOCKS4A_PROXY (proxy)->supports_hostname; |
443 | 0 | } |
444 | | |
445 | | static void |
446 | | g_socks4a_proxy_class_init (GSocks4aProxyClass *class) |
447 | 0 | { |
448 | 0 | GObjectClass *object_class; |
449 | |
|
450 | 0 | object_class = (GObjectClass *) class; |
451 | 0 | object_class->finalize = g_socks4a_proxy_finalize; |
452 | 0 | } |
453 | | |
454 | | static void |
455 | | g_socks4a_proxy_iface_init (GProxyInterface *proxy_iface) |
456 | 0 | { |
457 | 0 | proxy_iface->connect = g_socks4a_proxy_connect; |
458 | 0 | proxy_iface->connect_async = g_socks4a_proxy_connect_async; |
459 | 0 | proxy_iface->connect_finish = g_socks4a_proxy_connect_finish; |
460 | 0 | proxy_iface->supports_hostname = g_socks4a_proxy_supports_hostname; |
461 | 0 | } |