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