Remove mime entry from docs.
[pidgin-git.git] / libpurple / proxy.c
blob656c05c9fdb9c2c5f00461b28cde95ba698f578d
1 /* purple
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program 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
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 /* this is a little piece of code to handle proxy connection */
24 /* it is intended to : 1st handle http proxy, using the CONNECT command
25 , 2nd provide an easy way to add socks support
26 , 3rd draw women to it like flies to honey */
27 #define _PURPLE_PROXY_C_
29 #include "internal.h"
30 #include "debug.h"
31 #include "http.h"
32 #include "notify.h"
33 #include "prefs.h"
34 #include "proxy.h"
35 #include "purple-gio.h"
36 #include "util.h"
38 #include <gio/gio.h>
40 struct _PurpleProxyInfo
42 PurpleProxyType type; /* The proxy type. */
44 char *host; /* The host. */
45 int port; /* The port number. */
46 char *username; /* The username. */
47 char *password; /* The password. */
50 struct _PurpleProxyConnectData {
51 void *handle;
52 PurpleProxyConnectFunction connect_cb;
53 gpointer data;
54 gchar *host;
55 int port;
56 int fd;
57 PurpleProxyInfo *gpi;
59 GCancellable *cancellable;
62 static PurpleProxyInfo *global_proxy_info = NULL;
64 static GSList *handles = NULL;
67 * TODO: Eventually (GObjectification) this bad boy will be removed, because it is
68 * a gross fix for a crashy problem.
70 #define PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data) g_slist_find(handles, connect_data)
72 /**************************************************************************
73 * Proxy structure API
74 **************************************************************************/
75 PurpleProxyInfo *
76 purple_proxy_info_new(void)
78 return g_new0(PurpleProxyInfo, 1);
81 static PurpleProxyInfo *
82 purple_proxy_info_copy(PurpleProxyInfo *info)
84 PurpleProxyInfo *copy;
86 g_return_val_if_fail(info != NULL, NULL);
88 copy = purple_proxy_info_new();
89 copy->type = info->type;
90 copy->host = g_strdup(info->host);
91 copy->port = info->port;
92 copy->username = g_strdup(info->username);
93 copy->password = g_strdup(info->password);
95 return copy;
98 void
99 purple_proxy_info_destroy(PurpleProxyInfo *info)
101 g_return_if_fail(info != NULL);
103 g_free(info->host);
104 g_free(info->username);
105 g_free(info->password);
107 g_free(info);
110 void
111 purple_proxy_info_set_proxy_type(PurpleProxyInfo *info, PurpleProxyType type)
113 g_return_if_fail(info != NULL);
115 info->type = type;
118 void
119 purple_proxy_info_set_host(PurpleProxyInfo *info, const char *host)
121 g_return_if_fail(info != NULL);
123 g_free(info->host);
124 info->host = g_strdup(host);
127 void
128 purple_proxy_info_set_port(PurpleProxyInfo *info, int port)
130 g_return_if_fail(info != NULL);
132 info->port = port;
135 void
136 purple_proxy_info_set_username(PurpleProxyInfo *info, const char *username)
138 g_return_if_fail(info != NULL);
140 g_free(info->username);
141 info->username = g_strdup(username);
144 void
145 purple_proxy_info_set_password(PurpleProxyInfo *info, const char *password)
147 g_return_if_fail(info != NULL);
149 g_free(info->password);
150 info->password = g_strdup(password);
153 PurpleProxyType
154 purple_proxy_info_get_proxy_type(const PurpleProxyInfo *info)
156 g_return_val_if_fail(info != NULL, PURPLE_PROXY_NONE);
158 return info->type;
161 const char *
162 purple_proxy_info_get_host(const PurpleProxyInfo *info)
164 g_return_val_if_fail(info != NULL, NULL);
166 return info->host;
170 purple_proxy_info_get_port(const PurpleProxyInfo *info)
172 g_return_val_if_fail(info != NULL, 0);
174 return info->port;
177 const char *
178 purple_proxy_info_get_username(const PurpleProxyInfo *info)
180 g_return_val_if_fail(info != NULL, NULL);
182 return info->username;
185 const char *
186 purple_proxy_info_get_password(const PurpleProxyInfo *info)
188 g_return_val_if_fail(info != NULL, NULL);
190 return info->password;
193 G_DEFINE_BOXED_TYPE(PurpleProxyInfo, purple_proxy_info,
194 purple_proxy_info_copy, purple_proxy_info_destroy);
196 /**************************************************************************
197 * Global Proxy API
198 **************************************************************************/
199 PurpleProxyInfo *
200 purple_global_proxy_get_info(void)
202 return global_proxy_info;
205 void
206 purple_global_proxy_set_info(PurpleProxyInfo *info)
208 g_return_if_fail(info != NULL);
210 purple_proxy_info_destroy(global_proxy_info);
212 global_proxy_info = info;
216 /* index in gproxycmds below, keep them in sync */
217 #define GNOME_PROXY_MODE 0
218 #define GNOME_PROXY_USE_SAME_PROXY 1
219 #define GNOME_PROXY_SOCKS_HOST 2
220 #define GNOME_PROXY_SOCKS_PORT 3
221 #define GNOME_PROXY_HTTP_HOST 4
222 #define GNOME_PROXY_HTTP_PORT 5
223 #define GNOME_PROXY_HTTP_USER 6
224 #define GNOME_PROXY_HTTP_PASS 7
225 #define GNOME2_CMDS 0
226 #define GNOME3_CMDS 1
228 /* detect proxy settings for gnome2/gnome3 */
229 static const char* gproxycmds[][2] = {
230 { "gconftool-2 -g /system/proxy/mode" , "gsettings get org.gnome.system.proxy mode" },
231 { "gconftool-2 -g /system/http_proxy/use_same_proxy", "gsettings get org.gnome.system.proxy use-same-proxy" },
232 { "gconftool-2 -g /system/proxy/socks_host", "gsettings get org.gnome.system.proxy.socks host" },
233 { "gconftool-2 -g /system/proxy/socks_port", "gsettings get org.gnome.system.proxy.socks port" },
234 { "gconftool-2 -g /system/http_proxy/host", "gsettings get org.gnome.system.proxy.http host" },
235 { "gconftool-2 -g /system/http_proxy/port", "gsettings get org.gnome.system.proxy.http port"},
236 { "gconftool-2 -g /system/http_proxy/authentication_user", "gsettings get org.gnome.system.proxy.http authentication-user" },
237 { "gconftool-2 -g /system/http_proxy/authentication_password", "gsettings get org.gnome.system.proxy.http authentication-password" },
241 * purple_gnome_proxy_get_parameter:
242 * @parameter: One of the GNOME_PROXY_x constants defined above
243 * @gnome_version: GNOME2_CMDS or GNOME3_CMDS
245 * This is a utility function used to retrieve proxy parameter values from
246 * GNOME 2/3 environment.
248 * Returns: The value of requested proxy parameter
250 static char *
251 purple_gnome_proxy_get_parameter(guint8 parameter, guint8 gnome_version)
253 gchar *param, *err;
254 size_t param_len;
256 if (parameter > GNOME_PROXY_HTTP_PASS)
257 return NULL;
258 if (gnome_version > GNOME3_CMDS)
259 return NULL;
261 if (!g_spawn_command_line_sync(gproxycmds[parameter][gnome_version],
262 &param, &err, NULL, NULL))
263 return NULL;
264 g_free(err);
266 g_strstrip(param);
267 if (param[0] == '\'' || param[0] == '\"') {
268 param_len = strlen(param);
269 memmove(param, param + 1, param_len); /* copy last \0 too */
270 --param_len;
271 if (param_len > 0 && (param[param_len - 1] == '\'' || param[param_len - 1] == '\"'))
272 param[param_len - 1] = '\0';
273 g_strstrip(param);
276 return param;
279 static PurpleProxyInfo *
280 purple_gnome_proxy_get_info(void)
282 static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
283 gboolean use_same_proxy = FALSE;
284 gchar *tmp;
285 guint8 gnome_version = GNOME3_CMDS;
287 tmp = g_find_program_in_path("gsettings");
288 if (tmp == NULL) {
289 tmp = g_find_program_in_path("gconftool-2");
290 gnome_version = GNOME2_CMDS;
292 if (tmp == NULL)
293 return purple_global_proxy_get_info();
295 g_free(tmp);
297 /* Check whether to use a proxy. */
298 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_MODE, gnome_version);
299 if (!tmp)
300 return purple_global_proxy_get_info();
302 if (purple_strequal(tmp, "none")) {
303 info.type = PURPLE_PROXY_NONE;
304 g_free(tmp);
305 return &info;
308 if (!purple_strequal(tmp, "manual")) {
309 /* Unknown setting. Fallback to using our global proxy settings. */
310 g_free(tmp);
311 return purple_global_proxy_get_info();
314 g_free(tmp);
316 /* Free the old fields */
317 g_free(info.host);
318 info.host = NULL;
319 g_free(info.username);
320 info.username = NULL;
321 g_free(info.password);
322 info.password = NULL;
324 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_USE_SAME_PROXY, gnome_version);
325 if (!tmp)
326 return purple_global_proxy_get_info();
328 if (purple_strequal(tmp, "true"))
329 use_same_proxy = TRUE;
331 g_free(tmp);
333 if (!use_same_proxy) {
334 info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_HOST, gnome_version);
335 if (!info.host)
336 return purple_global_proxy_get_info();
339 if (!use_same_proxy && (info.host != NULL) && (*info.host != '\0')) {
340 info.type = PURPLE_PROXY_SOCKS5;
341 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_PORT, gnome_version);
342 if (!tmp) {
343 g_free(info.host);
344 info.host = NULL;
345 return purple_global_proxy_get_info();
347 info.port = atoi(tmp);
348 g_free(tmp);
349 } else {
350 g_free(info.host);
351 info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_HOST, gnome_version);
352 if (!info.host)
353 return purple_global_proxy_get_info();
355 /* If we get this far then we know we're using an HTTP proxy */
356 info.type = PURPLE_PROXY_HTTP;
358 if (*info.host == '\0')
360 purple_debug_info("proxy", "Gnome proxy settings are set to "
361 "'manual' but no suitable proxy server is specified. Using "
362 "Pidgin's proxy settings instead.\n");
363 g_free(info.host);
364 info.host = NULL;
365 return purple_global_proxy_get_info();
368 info.username = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_USER, gnome_version);
369 if (!info.username)
371 g_free(info.host);
372 info.host = NULL;
373 return purple_global_proxy_get_info();
376 info.password = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PASS, gnome_version);
377 if (!info.password)
379 g_free(info.host);
380 info.host = NULL;
381 g_free(info.username);
382 info.username = NULL;
383 return purple_global_proxy_get_info();
386 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PORT, gnome_version);
387 if (!tmp)
389 g_free(info.host);
390 info.host = NULL;
391 g_free(info.username);
392 info.username = NULL;
393 g_free(info.password);
394 info.password = NULL;
395 return purple_global_proxy_get_info();
397 info.port = atoi(tmp);
398 g_free(tmp);
401 return &info;
404 #ifdef _WIN32
406 typedef BOOL (CALLBACK* LPFNWINHTTPGETIEPROXYCONFIG)(/*IN OUT*/ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* pProxyConfig);
408 /* This modifies "host" in-place evilly */
409 static void
410 _proxy_fill_hostinfo(PurpleProxyInfo *info, char *host, int default_port)
412 int port = default_port;
413 char *d;
415 d = g_strrstr(host, ":");
416 if (d) {
417 *d = '\0';
419 d++;
420 if (*d)
421 sscanf(d, "%d", &port);
423 if (port == 0)
424 port = default_port;
427 purple_proxy_info_set_host(info, host);
428 purple_proxy_info_set_port(info, port);
431 static PurpleProxyInfo *
432 purple_win32_proxy_get_info(void)
434 static LPFNWINHTTPGETIEPROXYCONFIG MyWinHttpGetIEProxyConfig = NULL;
435 static gboolean loaded = FALSE;
436 static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
438 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_proxy_config;
440 if (!loaded) {
441 loaded = TRUE;
442 MyWinHttpGetIEProxyConfig = (LPFNWINHTTPGETIEPROXYCONFIG)
443 wpurple_find_and_loadproc("winhttp.dll", "WinHttpGetIEProxyConfigForCurrentUser");
444 if (!MyWinHttpGetIEProxyConfig)
445 purple_debug_warning("proxy", "Unable to read Windows Proxy Settings.\n");
448 if (!MyWinHttpGetIEProxyConfig)
449 return NULL;
451 ZeroMemory(&ie_proxy_config, sizeof(ie_proxy_config));
452 if (!MyWinHttpGetIEProxyConfig(&ie_proxy_config)) {
453 purple_debug_error("proxy", "Error reading Windows Proxy Settings(%lu).\n", GetLastError());
454 return NULL;
457 /* We can't do much if it is autodetect*/
458 if (ie_proxy_config.fAutoDetect) {
459 purple_debug_error("proxy", "Windows Proxy Settings set to autodetect (not supported).\n");
461 /* TODO: For 3.0.0 we'll revisit this (maybe)*/
463 return NULL;
465 } else if (ie_proxy_config.lpszProxy) {
466 gchar *proxy_list = g_utf16_to_utf8(ie_proxy_config.lpszProxy, -1,
467 NULL, NULL, NULL);
469 /* We can't do anything about the bypass list, as we don't have the url */
470 /* TODO: For 3.0.0 we'll revisit this*/
472 /* There are proxy settings for several protocols */
473 if (proxy_list && *proxy_list) {
474 char *specific = NULL, *tmp;
476 /* If there is only a global proxy, which means "HTTP" */
477 if (!strchr(proxy_list, ';') || (specific = g_strstr_len(proxy_list, -1, "http=")) != NULL) {
479 if (specific) {
480 specific += strlen("http=");
481 tmp = strchr(specific, ';');
482 if (tmp)
483 *tmp = '\0';
484 /* specific now points the proxy server (and port) */
485 } else
486 specific = proxy_list;
488 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_HTTP);
489 _proxy_fill_hostinfo(&info, specific, 80);
490 /* TODO: is there a way to set the username/password? */
491 purple_proxy_info_set_username(&info, NULL);
492 purple_proxy_info_set_password(&info, NULL);
494 purple_debug_info("proxy", "Windows Proxy Settings: HTTP proxy: '%s:%d'.\n",
495 purple_proxy_info_get_host(&info),
496 purple_proxy_info_get_port(&info));
498 } else if ((specific = g_strstr_len(proxy_list, -1, "socks=")) != NULL) {
500 specific += strlen("socks=");
501 tmp = strchr(specific, ';');
502 if (tmp)
503 *tmp = '\0';
504 /* specific now points the proxy server (and port) */
506 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_SOCKS5);
507 _proxy_fill_hostinfo(&info, specific, 1080);
508 /* TODO: is there a way to set the username/password? */
509 purple_proxy_info_set_username(&info, NULL);
510 purple_proxy_info_set_password(&info, NULL);
512 purple_debug_info("proxy", "Windows Proxy Settings: SOCKS5 proxy: '%s:%d'.\n",
513 purple_proxy_info_get_host(&info),
514 purple_proxy_info_get_port(&info));
516 } else {
518 purple_debug_info("proxy", "Windows Proxy Settings: No supported proxy specified.\n");
520 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_NONE);
525 /* TODO: Fix API to be able look at proxy bypass settings */
527 g_free(proxy_list);
528 } else {
529 purple_debug_info("proxy", "No Windows proxy set.\n");
530 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_NONE);
533 if (ie_proxy_config.lpszAutoConfigUrl)
534 GlobalFree(ie_proxy_config.lpszAutoConfigUrl);
535 if (ie_proxy_config.lpszProxy)
536 GlobalFree(ie_proxy_config.lpszProxy);
537 if (ie_proxy_config.lpszProxyBypass)
538 GlobalFree(ie_proxy_config.lpszProxyBypass);
540 return &info;
542 #endif
545 /**************************************************************************
546 * Proxy API
547 **************************************************************************/
550 * Whoever calls this needs to have called
551 * purple_proxy_connect_data_disconnect() beforehand.
553 static void
554 purple_proxy_connect_data_destroy(PurpleProxyConnectData *connect_data)
556 if (!PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data))
557 return;
559 handles = g_slist_remove(handles, connect_data);
561 if(G_IS_CANCELLABLE(connect_data->cancellable)) {
562 g_cancellable_cancel(connect_data->cancellable);
564 g_object_unref(G_OBJECT(connect_data->cancellable));
566 connect_data->cancellable = NULL;
569 g_free(connect_data->host);
570 g_free(connect_data);
574 * purple_proxy_connect_data_disconnect:
575 * @error_message: An error message explaining why the connection
576 * failed. This will be passed to the callback function
577 * specified in the call to purple_proxy_connect(). If the
578 * connection was successful then pass in null.
580 * Free all information dealing with a connection attempt and
581 * reset the connect_data to prepare for it to try to connect
582 * to another IP address.
584 * If an error message is passed in, then we know the connection
585 * attempt failed. If so, we call the callback with the given
586 * error message, then destroy the connect_data.
588 static void
589 purple_proxy_connect_data_disconnect(PurpleProxyConnectData *connect_data, const gchar *error_message)
591 if (connect_data->fd >= 0)
593 close(connect_data->fd);
594 connect_data->fd = -1;
597 if (error_message != NULL)
599 purple_debug_error("proxy", "Connection attempt failed: %s\n",
600 error_message);
602 /* Everything failed! Tell the originator of the request. */
603 connect_data->connect_cb(connect_data->data, -1, error_message);
604 purple_proxy_connect_data_destroy(connect_data);
608 static void
609 purple_proxy_connect_data_connected(PurpleProxyConnectData *connect_data)
611 purple_debug_info("proxy", "Connected to %s:%d.\n",
612 connect_data->host, connect_data->port);
614 connect_data->connect_cb(connect_data->data, connect_data->fd, NULL);
617 * We've passed the file descriptor to the protocol, so it's no longer
618 * our responsibility, and we should be careful not to free it when
619 * we destroy the connect_data.
621 connect_data->fd = -1;
623 purple_proxy_connect_data_disconnect(connect_data, NULL);
624 purple_proxy_connect_data_destroy(connect_data);
627 PurpleProxyInfo *
628 purple_proxy_get_setup(PurpleAccount *account)
630 PurpleProxyInfo *gpi = NULL;
631 const gchar *tmp;
633 /* This is used as a fallback so we don't overwrite the selected proxy type */
634 static PurpleProxyInfo *tmp_none_proxy_info = NULL;
635 if (!tmp_none_proxy_info) {
636 tmp_none_proxy_info = purple_proxy_info_new();
637 purple_proxy_info_set_proxy_type(tmp_none_proxy_info, PURPLE_PROXY_NONE);
640 if (account && purple_account_get_proxy_info(account) != NULL) {
641 gpi = purple_account_get_proxy_info(account);
642 if (purple_proxy_info_get_proxy_type(gpi) == PURPLE_PROXY_USE_GLOBAL)
643 gpi = NULL;
645 if (gpi == NULL) {
646 if (purple_running_gnome())
647 gpi = purple_gnome_proxy_get_info();
648 else
649 gpi = purple_global_proxy_get_info();
652 if (purple_proxy_info_get_proxy_type(gpi) == PURPLE_PROXY_USE_ENVVAR) {
653 if ((tmp = g_getenv("HTTP_PROXY")) != NULL ||
654 (tmp = g_getenv("http_proxy")) != NULL ||
655 (tmp = g_getenv("HTTPPROXY")) != NULL)
657 PurpleHttpURL *url;
659 /* http_proxy-format:
660 * export http_proxy="http://user:passwd@your.proxy.server:port/"
662 url = purple_http_url_parse(tmp);
663 if (!url) {
664 purple_debug_warning("proxy", "Couldn't parse URL\n");
665 return gpi;
668 purple_proxy_info_set_host(gpi, purple_http_url_get_host(url));
669 purple_proxy_info_set_username(gpi, purple_http_url_get_username(url));
670 purple_proxy_info_set_password(gpi, purple_http_url_get_password(url));
671 purple_proxy_info_set_port(gpi, purple_http_url_get_port(url));
673 /* XXX: Do we want to skip this step if user/password/port were part of url? */
674 if ((tmp = g_getenv("HTTP_PROXY_USER")) != NULL ||
675 (tmp = g_getenv("http_proxy_user")) != NULL ||
676 (tmp = g_getenv("HTTPPROXYUSER")) != NULL)
677 purple_proxy_info_set_username(gpi, tmp);
679 if ((tmp = g_getenv("HTTP_PROXY_PASS")) != NULL ||
680 (tmp = g_getenv("http_proxy_pass")) != NULL ||
681 (tmp = g_getenv("HTTPPROXYPASS")) != NULL)
682 purple_proxy_info_set_password(gpi, tmp);
684 if ((tmp = g_getenv("HTTP_PROXY_PORT")) != NULL ||
685 (tmp = g_getenv("http_proxy_port")) != NULL ||
686 (tmp = g_getenv("HTTPPROXYPORT")) != NULL)
687 purple_proxy_info_set_port(gpi, atoi(tmp));
688 } else {
689 #ifdef _WIN32
690 PurpleProxyInfo *wgpi;
691 if ((wgpi = purple_win32_proxy_get_info()) != NULL)
692 return wgpi;
693 #endif
694 /* no proxy environment variable found, don't use a proxy */
695 purple_debug_info("proxy", "No environment settings found, not using a proxy\n");
696 gpi = tmp_none_proxy_info;
701 return gpi;
704 /* Grabbed duplicate_fd() from GLib's testcases (gio/tests/socket.c).
705 * Can be dropped once this API has been converted to Gio.
707 static int
708 duplicate_fd (int fd)
710 #ifdef G_OS_WIN32
711 HANDLE newfd;
713 if (!DuplicateHandle (GetCurrentProcess (),
714 (HANDLE)fd,
715 GetCurrentProcess (),
716 &newfd,
718 FALSE,
719 DUPLICATE_SAME_ACCESS))
721 return -1;
724 return (int)newfd;
725 #else
726 return dup (fd);
727 #endif
729 /* End function grabbed from GLib */
731 static void
732 connect_to_host_cb(GObject *source, GAsyncResult *res, gpointer user_data)
734 PurpleProxyConnectData *connect_data = user_data;
735 GSocketConnection *conn;
736 GError *error = NULL;
737 GSocket *socket;
739 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
740 res, &error);
741 if (conn == NULL) {
742 /* Ignore cancelled error as that signifies connect_data has
743 * been freed
745 if (!g_error_matches(error, G_IO_ERROR,
746 G_IO_ERROR_CANCELLED)) {
747 purple_debug_error("proxy", "Unable to connect to "
748 "destination host: %s\n",
749 error->message);
750 purple_proxy_connect_data_disconnect(connect_data,
751 "Unable to connect to destination "
752 "host.\n");
755 g_clear_error(&error);
756 return;
759 socket = g_socket_connection_get_socket(conn);
760 g_assert(socket != NULL);
762 /* Duplicate the file descriptor, and then free the connection.
763 * libpurple's proxy code doesn't keep an object around for the
764 * lifetime of the connection. Therefore, in order to not leak
765 * memory, the GSocketConnection must be freed here. In order
766 * to avoid the double close/free of the file descriptor, the
767 * file descriptor is duplicated.
769 connect_data->fd = duplicate_fd(g_socket_get_fd(socket));
770 g_object_unref(conn);
772 purple_proxy_connect_data_connected(connect_data);
775 PurpleProxyConnectData *
776 purple_proxy_connect(void *handle, PurpleAccount *account,
777 const char *host, int port,
778 PurpleProxyConnectFunction connect_cb, gpointer data)
780 PurpleProxyConnectData *connect_data;
781 GSocketClient *client;
782 GError *error = NULL;
784 g_return_val_if_fail(host != NULL, NULL);
785 g_return_val_if_fail(port > 0, NULL);
786 g_return_val_if_fail(connect_cb != NULL, NULL);
788 client = purple_gio_socket_client_new(account, &error);
790 if (client == NULL) {
791 /* Assume it's a proxy error */
792 purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
793 error->message,
794 purple_request_cpar_from_account(account));
795 g_clear_error(&error);
796 return NULL;
799 connect_data = g_new0(PurpleProxyConnectData, 1);
800 connect_data->fd = -1;
801 connect_data->handle = handle;
802 connect_data->connect_cb = connect_cb;
803 connect_data->data = data;
804 connect_data->host = g_strdup(host);
805 connect_data->port = port;
806 connect_data->gpi = purple_proxy_get_setup(account);
807 connect_data->cancellable = g_cancellable_new();
809 purple_debug_info("proxy", "Attempting connection to %s:%u\n",
810 host, port);
812 g_socket_client_connect_to_host_async(client, host, port,
813 connect_data->cancellable, connect_to_host_cb,
814 connect_data);
815 g_object_unref(client);
817 handles = g_slist_prepend(handles, connect_data);
819 return connect_data;
822 static void
823 socks5_proxy_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
825 PurpleProxyConnectData *connect_data = user_data;
826 GIOStream *stream;
827 GError *error = NULL;
828 GSocket *socket;
830 stream = g_proxy_connect_finish(G_PROXY(source), res, &error);
832 if (stream == NULL) {
833 /* Ignore cancelled error as that signifies connect_data has
834 * been freed
836 if (!g_error_matches(error, G_IO_ERROR,
837 G_IO_ERROR_CANCELLED)) {
838 purple_debug_error("proxy", "Unable to connect to "
839 "destination host: %s\n",
840 error->message);
841 purple_proxy_connect_data_disconnect(connect_data,
842 "Unable to connect to destination "
843 "host.\n");
846 g_clear_error(&error);
847 return;
850 if (!G_IS_SOCKET_CONNECTION(stream)) {
851 purple_debug_error("proxy",
852 "GProxy didn't return a GSocketConnection.\n");
853 purple_proxy_connect_data_disconnect(connect_data,
854 "GProxy didn't return a GSocketConnection.\n");
855 g_object_unref(stream);
856 return;
859 socket = g_socket_connection_get_socket(G_SOCKET_CONNECTION(stream));
861 /* Duplicate the file descriptor, and then free the connection.
862 * libpurple's proxy code doesn't keep an object around for the
863 * lifetime of the connection. Therefore, in order to not leak
864 * memory, the GSocketConnection (aka GIOStream here) must be
865 * freed here. In order to avoid the double close/free of the
866 * file descriptor, the file descriptor is duplicated.
868 connect_data->fd = duplicate_fd(g_socket_get_fd(socket));
869 g_object_unref(stream);
871 purple_proxy_connect_data_connected(connect_data);
874 /* This is called when we connect to the SOCKS5 proxy server (through any
875 * relevant account proxy)
877 static void
878 socks5_connect_to_host_cb(GObject *source, GAsyncResult *res,
879 gpointer user_data)
881 PurpleProxyConnectData *connect_data = user_data;
882 GSocketConnection *conn;
883 GError *error = NULL;
884 GProxy *proxy;
885 PurpleProxyInfo *info;
886 GSocketAddress *addr;
887 GInetSocketAddress *inet_addr;
888 GSocketAddress *proxy_addr;
890 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
891 res, &error);
892 if (conn == NULL) {
893 /* Ignore cancelled error as that signifies connect_data has
894 * been freed
896 if (!g_error_matches(error, G_IO_ERROR,
897 G_IO_ERROR_CANCELLED)) {
898 purple_debug_error("proxy", "Unable to connect to "
899 "SOCKS5 host: %s\n", error->message);
900 purple_proxy_connect_data_disconnect(connect_data,
901 "Unable to connect to SOCKS5 host.\n");
904 g_clear_error(&error);
905 return;
908 proxy = g_proxy_get_default_for_protocol("socks5");
909 if (proxy == NULL) {
910 purple_debug_error("proxy", "SOCKS5 proxy backend missing.\n");
911 purple_proxy_connect_data_disconnect(connect_data,
912 "SOCKS5 proxy backend missing.\n");
913 g_object_unref(conn);
914 return;
917 info = connect_data->gpi;
919 addr = g_socket_connection_get_remote_address(conn, &error);
920 if (addr == NULL) {
921 purple_debug_error("proxy", "Unable to retrieve SOCKS5 host "
922 "address from connection: %s\n",
923 error->message);
924 purple_proxy_connect_data_disconnect(connect_data,
925 "Unable to retrieve SOCKS5 host address from "
926 "connection");
927 g_object_unref(conn);
928 g_object_unref(proxy);
929 g_clear_error(&error);
930 return;
933 inet_addr = G_INET_SOCKET_ADDRESS(addr);
935 proxy_addr = g_proxy_address_new(
936 g_inet_socket_address_get_address(inet_addr),
937 g_inet_socket_address_get_port(inet_addr),
938 "socks5", connect_data->host, connect_data->port,
939 purple_proxy_info_get_username(info),
940 purple_proxy_info_get_password(info));
941 g_object_unref(inet_addr);
943 purple_debug_info("proxy", "Initiating SOCKS5 negotiation.\n");
945 purple_debug_info("proxy",
946 "Connecting to %s:%d via %s:%d using SOCKS5\n",
947 connect_data->host, connect_data->port,
948 purple_proxy_info_get_host(connect_data->gpi),
949 purple_proxy_info_get_port(connect_data->gpi));
951 g_proxy_connect_async(proxy, G_IO_STREAM(conn),
952 G_PROXY_ADDRESS(proxy_addr),
953 connect_data->cancellable,
954 socks5_proxy_connect_cb, connect_data);
955 g_object_unref(proxy_addr);
956 g_object_unref(conn);
957 g_object_unref(proxy);
961 * Combine some of this code with purple_proxy_connect()
963 PurpleProxyConnectData *
964 purple_proxy_connect_socks5_account(void *handle, PurpleAccount *account,
965 PurpleProxyInfo *gpi,
966 const char *host, int port,
967 PurpleProxyConnectFunction connect_cb,
968 gpointer data)
970 PurpleProxyConnectData *connect_data;
971 GSocketClient *client;
972 GError *error = NULL;
974 g_return_val_if_fail(host != NULL, NULL);
975 g_return_val_if_fail(port >= 0, NULL);
976 g_return_val_if_fail(connect_cb != NULL, NULL);
978 client = purple_gio_socket_client_new(account, &error);
980 if (client == NULL) {
981 /* Assume it's a proxy error */
982 purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
983 error->message,
984 purple_request_cpar_from_account(account));
985 g_clear_error(&error);
986 return NULL;
989 connect_data = g_new0(PurpleProxyConnectData, 1);
990 connect_data->fd = -1;
991 connect_data->handle = handle;
992 connect_data->connect_cb = connect_cb;
993 connect_data->data = data;
994 connect_data->host = g_strdup(host);
995 connect_data->port = port;
996 connect_data->gpi = gpi;
997 connect_data->cancellable = g_cancellable_new();
999 purple_debug_info("proxy",
1000 "Connecting to %s:%d via %s:%d using SOCKS5\n",
1001 connect_data->host, connect_data->port,
1002 purple_proxy_info_get_host(connect_data->gpi),
1003 purple_proxy_info_get_port(connect_data->gpi));
1005 g_socket_client_connect_to_host_async(client,
1006 purple_proxy_info_get_host(connect_data->gpi),
1007 purple_proxy_info_get_port(connect_data->gpi),
1008 connect_data->cancellable, socks5_connect_to_host_cb,
1009 connect_data);
1010 g_object_unref(client);
1012 handles = g_slist_prepend(handles, connect_data);
1014 return connect_data;
1017 void
1018 purple_proxy_connect_cancel(PurpleProxyConnectData *connect_data)
1020 g_return_if_fail(connect_data != NULL);
1022 purple_proxy_connect_data_disconnect(connect_data, NULL);
1023 purple_proxy_connect_data_destroy(connect_data);
1026 void
1027 purple_proxy_connect_cancel_with_handle(void *handle)
1029 GSList *l, *l_next;
1031 for (l = handles; l != NULL; l = l_next) {
1032 PurpleProxyConnectData *connect_data = l->data;
1034 l_next = l->next;
1036 if (connect_data->handle == handle)
1037 purple_proxy_connect_cancel(connect_data);
1041 GProxyResolver *
1042 purple_proxy_get_proxy_resolver(PurpleAccount *account, GError **error)
1044 PurpleProxyInfo *info = purple_proxy_get_setup(account);
1045 const gchar *protocol;
1046 const gchar *username;
1047 const gchar *password;
1048 gchar *auth;
1049 gchar *proxy;
1050 GProxyResolver *resolver;
1052 if (purple_proxy_info_get_proxy_type(info) == PURPLE_PROXY_NONE) {
1053 /* Return an empty simple resolver, which will resolve on direct
1054 * connection. */
1055 return g_simple_proxy_resolver_new(NULL, NULL);
1058 switch (purple_proxy_info_get_proxy_type(info))
1060 /* PURPLE_PROXY_NONE already handled above */
1062 case PURPLE_PROXY_USE_ENVVAR:
1063 /* Intentional passthrough */
1064 case PURPLE_PROXY_HTTP:
1065 protocol = "http";
1066 break;
1067 case PURPLE_PROXY_SOCKS4:
1068 protocol = "socks4";
1069 break;
1070 case PURPLE_PROXY_SOCKS5:
1071 /* Intentional passthrough */
1072 case PURPLE_PROXY_TOR:
1073 protocol = "socks5";
1074 break;
1076 default:
1077 g_set_error(error, PURPLE_CONNECTION_ERROR,
1078 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1079 _("Invalid Proxy type (%d) specified"),
1080 purple_proxy_info_get_proxy_type(info));
1081 return NULL;
1085 if (purple_proxy_info_get_host(info) == NULL ||
1086 purple_proxy_info_get_port(info) <= 0) {
1087 g_set_error_literal(error, PURPLE_CONNECTION_ERROR,
1088 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1089 _("Either the host name or port number "
1090 "specified for your given proxy type is "
1091 "invalid."));
1092 return NULL;
1095 /* Everything checks out. Create and return the GProxyResolver */
1097 username = purple_proxy_info_get_username(info);
1098 password = purple_proxy_info_get_password(info);
1100 /* Username and password are optional */
1101 if (username != NULL && password != NULL) {
1102 auth = g_strdup_printf("%s:%s@", username, password);
1103 } else if (username != NULL) {
1104 auth = g_strdup_printf("%s@", username);
1105 } else {
1106 auth = NULL;
1109 proxy = g_strdup_printf("%s://%s%s:%i", protocol,
1110 auth != NULL ? auth : "",
1111 purple_proxy_info_get_host(info),
1112 purple_proxy_info_get_port(info));
1113 g_free(auth);
1115 resolver = g_simple_proxy_resolver_new(proxy, NULL);
1116 g_free(proxy);
1118 return resolver;
1121 static void
1122 proxy_pref_cb(const char *name, PurplePrefType type,
1123 gconstpointer value, gpointer data)
1125 PurpleProxyInfo *info = purple_global_proxy_get_info();
1127 if (purple_strequal(name, "/purple/proxy/type")) {
1128 int proxytype;
1129 const char *type = value;
1131 if (purple_strequal(type, "none"))
1132 proxytype = PURPLE_PROXY_NONE;
1133 else if (purple_strequal(type, "http"))
1134 proxytype = PURPLE_PROXY_HTTP;
1135 else if (purple_strequal(type, "socks4"))
1136 proxytype = PURPLE_PROXY_SOCKS4;
1137 else if (purple_strequal(type, "socks5"))
1138 proxytype = PURPLE_PROXY_SOCKS5;
1139 else if (purple_strequal(type, "tor"))
1140 proxytype = PURPLE_PROXY_TOR;
1141 else if (purple_strequal(type, "envvar"))
1142 proxytype = PURPLE_PROXY_USE_ENVVAR;
1143 else
1144 proxytype = -1;
1146 purple_proxy_info_set_proxy_type(info, proxytype);
1147 } else if (purple_strequal(name, "/purple/proxy/host"))
1148 purple_proxy_info_set_host(info, value);
1149 else if (purple_strequal(name, "/purple/proxy/port"))
1150 purple_proxy_info_set_port(info, GPOINTER_TO_INT(value));
1151 else if (purple_strequal(name, "/purple/proxy/username"))
1152 purple_proxy_info_set_username(info, value);
1153 else if (purple_strequal(name, "/purple/proxy/password"))
1154 purple_proxy_info_set_password(info, value);
1157 void *
1158 purple_proxy_get_handle()
1160 static int handle;
1162 return &handle;
1165 void
1166 purple_proxy_init(void)
1168 void *handle;
1170 /* Initialize a default proxy info struct. */
1171 global_proxy_info = purple_proxy_info_new();
1173 /* Proxy */
1174 purple_prefs_add_none("/purple/proxy");
1175 purple_prefs_add_string("/purple/proxy/type", "none");
1176 purple_prefs_add_string("/purple/proxy/host", "");
1177 purple_prefs_add_int("/purple/proxy/port", 0);
1178 purple_prefs_add_string("/purple/proxy/username", "");
1179 purple_prefs_add_string("/purple/proxy/password", "");
1180 purple_prefs_add_bool("/purple/proxy/socks4_remotedns", FALSE);
1182 /* Setup callbacks for the preferences. */
1183 handle = purple_proxy_get_handle();
1184 purple_prefs_connect_callback(handle, "/purple/proxy/type", proxy_pref_cb,
1185 NULL);
1186 purple_prefs_connect_callback(handle, "/purple/proxy/host", proxy_pref_cb,
1187 NULL);
1188 purple_prefs_connect_callback(handle, "/purple/proxy/port", proxy_pref_cb,
1189 NULL);
1190 purple_prefs_connect_callback(handle, "/purple/proxy/username",
1191 proxy_pref_cb, NULL);
1192 purple_prefs_connect_callback(handle, "/purple/proxy/password",
1193 proxy_pref_cb, NULL);
1195 /* Load the initial proxy settings */
1196 purple_prefs_trigger_callback("/purple/proxy/type");
1197 purple_prefs_trigger_callback("/purple/proxy/host");
1198 purple_prefs_trigger_callback("/purple/proxy/port");
1199 purple_prefs_trigger_callback("/purple/proxy/username");
1200 purple_prefs_trigger_callback("/purple/proxy/password");
1203 void
1204 purple_proxy_uninit(void)
1206 while (handles != NULL)
1208 purple_proxy_connect_data_disconnect(handles->data, NULL);
1209 purple_proxy_connect_data_destroy(handles->data);
1212 purple_prefs_disconnect_by_handle(purple_proxy_get_handle());
1214 purple_proxy_info_destroy(global_proxy_info);
1215 global_proxy_info = NULL;