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 | #define SOCKS4_VERSION 4 |
39 | |
40 | #define SOCKS4_CMD_CONNECT 1 |
41 | #define SOCKS4_CMD_BIND 2 |
42 | |
43 | #define SOCKS4_MAX_LEN 255 |
44 | |
45 | #define SOCKS4_REP_VERSION 0 |
46 | #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 | { |
66 | /* must chain up */ |
67 | G_OBJECT_CLASS (g_socks4a_proxy_parent_class)->finalize (object); |
68 | } |
69 | |
70 | static void |
71 | g_socks4a_proxy_init (GSocks4aProxy *proxy) |
72 | { |
73 | proxy->supports_hostname = TRUE; |
74 | } |
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 | #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 | { |
90 | GInetAddress *addr; |
91 | guint len = 0; |
92 | gsize addr_len; |
93 | gboolean is_ip; |
94 | const gchar *ip; |
95 | |
96 | msg[len++] = SOCKS4_VERSION; |
97 | msg[len++] = SOCKS4_CMD_CONNECT; |
98 | |
99 | { |
100 | guint16 hp = g_htons (port); |
101 | memcpy (dest: msg + len, src: &hp, n: 2); |
102 | len += 2; |
103 | } |
104 | |
105 | is_ip = g_hostname_is_ip_address (hostname); |
106 | |
107 | if (is_ip) |
108 | ip = hostname; |
109 | else |
110 | ip = "0.0.0.1" ; |
111 | |
112 | addr = g_inet_address_new_from_string (string: ip); |
113 | addr_len = g_inet_address_get_native_size (address: addr); |
114 | |
115 | if (addr_len != 4) |
116 | { |
117 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_PROXY_FAILED, |
118 | _("SOCKSv4 does not support IPv6 address “%s”" ), |
119 | ip); |
120 | g_object_unref (object: addr); |
121 | return -1; |
122 | } |
123 | |
124 | memcpy (dest: msg + len, src: g_inet_address_to_bytes (address: addr), n: addr_len); |
125 | len += addr_len; |
126 | |
127 | g_object_unref (object: addr); |
128 | |
129 | if (username) |
130 | { |
131 | gsize user_len = strlen (s: username); |
132 | |
133 | if (user_len > SOCKS4_MAX_LEN) |
134 | { |
135 | g_set_error_literal (err: error, G_IO_ERROR, code: G_IO_ERROR_PROXY_FAILED, |
136 | _("Username is too long for SOCKSv4 protocol" )); |
137 | return -1; |
138 | } |
139 | |
140 | memcpy (dest: msg + len, src: username, n: user_len); |
141 | len += user_len; |
142 | } |
143 | |
144 | msg[len++] = '\0'; |
145 | |
146 | if (!is_ip) |
147 | { |
148 | gsize host_len = strlen (s: hostname); |
149 | |
150 | if (host_len > SOCKS4_MAX_LEN) |
151 | { |
152 | g_set_error (err: error, G_IO_ERROR, code: G_IO_ERROR_PROXY_FAILED, |
153 | _("Hostname “%s” is too long for SOCKSv4 protocol" ), |
154 | hostname); |
155 | return -1; |
156 | } |
157 | |
158 | memcpy (dest: msg + len, src: hostname, n: host_len); |
159 | len += host_len; |
160 | msg[len++] = '\0'; |
161 | } |
162 | |
163 | return len; |
164 | } |
165 | |
166 | /* |
167 | * +----+----+----+----+----+----+----+----+ |
168 | * | VN | CD | DSTPORT | DSTIP | |
169 | * +----+----+----+----+----+----+----+----+ |
170 | * 1 1 2 4 |
171 | */ |
172 | #define SOCKS4_CONN_REP_LEN 8 |
173 | static gboolean |
174 | parse_connect_reply (const guint8 *data, GError **error) |
175 | { |
176 | if (data[0] != SOCKS4_REP_VERSION) |
177 | { |
178 | g_set_error_literal (err: error, G_IO_ERROR, code: G_IO_ERROR_PROXY_FAILED, |
179 | _("The server is not a SOCKSv4 proxy server." )); |
180 | return FALSE; |
181 | } |
182 | |
183 | if (data[1] != SOCKS4_REP_GRANTED) |
184 | { |
185 | g_set_error_literal (err: error, G_IO_ERROR, code: G_IO_ERROR_PROXY_FAILED, |
186 | _("Connection through SOCKSv4 server was rejected" )); |
187 | return FALSE; |
188 | } |
189 | |
190 | return TRUE; |
191 | } |
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 | { |
200 | GInputStream *in; |
201 | GOutputStream *out; |
202 | const gchar *hostname; |
203 | guint16 port; |
204 | const gchar *username; |
205 | |
206 | hostname = g_proxy_address_get_destination_hostname (proxy: proxy_address); |
207 | port = g_proxy_address_get_destination_port (proxy: proxy_address); |
208 | username = g_proxy_address_get_username (proxy: proxy_address); |
209 | |
210 | in = g_io_stream_get_input_stream (stream: io_stream); |
211 | out = g_io_stream_get_output_stream (stream: io_stream); |
212 | |
213 | /* Send SOCKS4 connection request */ |
214 | { |
215 | guint8 msg[SOCKS4_CONN_MSG_LEN]; |
216 | gint len; |
217 | |
218 | len = set_connect_msg (msg, hostname, port, username, error); |
219 | |
220 | if (len < 0) |
221 | goto error; |
222 | |
223 | if (!g_output_stream_write_all (stream: out, buffer: msg, count: len, NULL, |
224 | cancellable, error)) |
225 | goto error; |
226 | } |
227 | |
228 | /* Read SOCKS4 response */ |
229 | { |
230 | guint8 data[SOCKS4_CONN_REP_LEN]; |
231 | |
232 | if (!g_input_stream_read_all (stream: in, buffer: data, SOCKS4_CONN_REP_LEN, NULL, |
233 | cancellable, error)) |
234 | goto error; |
235 | |
236 | if (!parse_connect_reply (data, error)) |
237 | goto error; |
238 | } |
239 | |
240 | return g_object_ref (io_stream); |
241 | |
242 | error: |
243 | return NULL; |
244 | } |
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 | { |
268 | g_object_unref (object: data->io_stream); |
269 | g_slice_free (ConnectAsyncData, data); |
270 | } |
271 | |
272 | static void |
273 | do_read (GAsyncReadyCallback callback, GTask *task, ConnectAsyncData *data) |
274 | { |
275 | GInputStream *in; |
276 | in = g_io_stream_get_input_stream (stream: data->io_stream); |
277 | g_input_stream_read_async (stream: in, |
278 | buffer: data->buffer + data->offset, |
279 | count: data->length - data->offset, |
280 | io_priority: g_task_get_priority (task), |
281 | cancellable: g_task_get_cancellable (task), |
282 | callback, user_data: task); |
283 | } |
284 | |
285 | static void |
286 | do_write (GAsyncReadyCallback callback, GTask *task, ConnectAsyncData *data) |
287 | { |
288 | GOutputStream *out; |
289 | out = g_io_stream_get_output_stream (stream: data->io_stream); |
290 | g_output_stream_write_async (stream: out, |
291 | buffer: data->buffer + data->offset, |
292 | count: data->length - data->offset, |
293 | io_priority: g_task_get_priority (task), |
294 | cancellable: g_task_get_cancellable (task), |
295 | callback, user_data: task); |
296 | } |
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 | { |
308 | GError *error = NULL; |
309 | GTask *task; |
310 | ConnectAsyncData *data; |
311 | const gchar *hostname; |
312 | guint16 port; |
313 | const gchar *username; |
314 | |
315 | data = g_slice_new0 (ConnectAsyncData); |
316 | data->io_stream = g_object_ref (io_stream); |
317 | |
318 | task = g_task_new (source_object: proxy, cancellable, callback, callback_data: user_data); |
319 | g_task_set_source_tag (task, g_socks4a_proxy_connect_async); |
320 | g_task_set_task_data (task, task_data: data, task_data_destroy: (GDestroyNotify) free_connect_data); |
321 | |
322 | hostname = g_proxy_address_get_destination_hostname (proxy: proxy_address); |
323 | port = g_proxy_address_get_destination_port (proxy: proxy_address); |
324 | username = g_proxy_address_get_username (proxy: proxy_address); |
325 | |
326 | data->buffer = g_malloc0 (SOCKS4_CONN_MSG_LEN); |
327 | data->length = set_connect_msg (msg: data->buffer, |
328 | hostname, port, username, |
329 | error: &error); |
330 | data->offset = 0; |
331 | |
332 | if (data->length < 0) |
333 | { |
334 | g_task_return_error (task, error); |
335 | g_object_unref (object: task); |
336 | } |
337 | else |
338 | { |
339 | do_write (callback: connect_msg_write_cb, task, data); |
340 | } |
341 | } |
342 | |
343 | static void |
344 | connect_msg_write_cb (GObject *source, |
345 | GAsyncResult *result, |
346 | gpointer user_data) |
347 | { |
348 | GTask *task = user_data; |
349 | ConnectAsyncData *data = g_task_get_task_data (task); |
350 | GError *error = NULL; |
351 | gssize written; |
352 | |
353 | written = g_output_stream_write_finish (G_OUTPUT_STREAM (source), |
354 | result, error: &error); |
355 | |
356 | if (written < 0) |
357 | { |
358 | g_task_return_error (task, error); |
359 | g_object_unref (object: task); |
360 | return; |
361 | } |
362 | |
363 | data->offset += written; |
364 | |
365 | if (data->offset == data->length) |
366 | { |
367 | g_free (mem: data->buffer); |
368 | |
369 | data->buffer = g_malloc0 (SOCKS4_CONN_REP_LEN); |
370 | data->length = SOCKS4_CONN_REP_LEN; |
371 | data->offset = 0; |
372 | |
373 | do_read (callback: connect_reply_read_cb, task, data); |
374 | } |
375 | else |
376 | { |
377 | do_write (callback: connect_msg_write_cb, task, data); |
378 | } |
379 | } |
380 | |
381 | static void |
382 | connect_reply_read_cb (GObject *source, |
383 | GAsyncResult *result, |
384 | gpointer user_data) |
385 | { |
386 | GTask *task = user_data; |
387 | ConnectAsyncData *data = g_task_get_task_data (task); |
388 | GError *error = NULL; |
389 | gssize read; |
390 | |
391 | read = g_input_stream_read_finish (G_INPUT_STREAM (source), |
392 | result, error: &error); |
393 | |
394 | if (read < 0) |
395 | { |
396 | g_task_return_error (task, error); |
397 | g_object_unref (object: task); |
398 | return; |
399 | } |
400 | |
401 | data->offset += read; |
402 | |
403 | if (data->offset == data->length) |
404 | { |
405 | if (!parse_connect_reply (data: data->buffer, error: &error)) |
406 | { |
407 | g_task_return_error (task, error); |
408 | g_object_unref (object: task); |
409 | return; |
410 | } |
411 | else |
412 | { |
413 | g_task_return_pointer (task, g_object_ref (data->io_stream), result_destroy: g_object_unref); |
414 | g_object_unref (object: task); |
415 | return; |
416 | } |
417 | } |
418 | else |
419 | { |
420 | do_read (callback: connect_reply_read_cb, task, data); |
421 | } |
422 | } |
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 | { |
433 | return g_task_propagate_pointer (G_TASK (result), error); |
434 | } |
435 | |
436 | static gboolean |
437 | g_socks4a_proxy_supports_hostname (GProxy *proxy) |
438 | { |
439 | return G_SOCKS4A_PROXY (proxy)->supports_hostname; |
440 | } |
441 | |
442 | static void |
443 | g_socks4a_proxy_class_init (GSocks4aProxyClass *class) |
444 | { |
445 | GObjectClass *object_class; |
446 | |
447 | object_class = (GObjectClass *) class; |
448 | object_class->finalize = g_socks4a_proxy_finalize; |
449 | } |
450 | |
451 | static void |
452 | g_socks4a_proxy_iface_init (GProxyInterface *proxy_iface) |
453 | { |
454 | proxy_iface->connect = g_socks4a_proxy_connect; |
455 | proxy_iface->connect_async = g_socks4a_proxy_connect_async; |
456 | proxy_iface->connect_finish = g_socks4a_proxy_connect_finish; |
457 | proxy_iface->supports_hostname = g_socks4a_proxy_supports_hostname; |
458 | } |
459 | |