zephyr: Remove unused defines and headers.
[pidgin-git.git] / libpurple / proxy.c
blobef284d9b688a6d3e5f7cec3cc80739778eb65c28
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 */
28 #include "internal.h"
29 #include "debug.h"
30 #include "http.h"
31 #include "notify.h"
32 #include "prefs.h"
33 #include "proxy.h"
34 #include "purple-gio.h"
35 #include "util.h"
37 #include <gio/gio.h>
39 struct _PurpleProxyInfo
41 PurpleProxyType type; /* The proxy type. */
43 char *host; /* The host. */
44 int port; /* The port number. */
45 char *username; /* The username. */
46 char *password; /* The password. */
49 struct _PurpleProxyConnectData {
50 void *handle;
51 PurpleProxyConnectFunction connect_cb;
52 gpointer data;
53 gchar *host;
54 int port;
55 int fd;
56 PurpleProxyInfo *gpi;
58 GCancellable *cancellable;
61 static PurpleProxyInfo *global_proxy_info = NULL;
63 static GSList *handles = NULL;
66 * TODO: Eventually (GObjectification) this bad boy will be removed, because it is
67 * a gross fix for a crashy problem.
69 #define PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data) g_slist_find(handles, connect_data)
71 /**************************************************************************
72 * Proxy structure API
73 **************************************************************************/
74 PurpleProxyInfo *
75 purple_proxy_info_new(void)
77 return g_new0(PurpleProxyInfo, 1);
80 static PurpleProxyInfo *
81 purple_proxy_info_copy(PurpleProxyInfo *info)
83 PurpleProxyInfo *copy;
85 g_return_val_if_fail(info != NULL, NULL);
87 copy = purple_proxy_info_new();
88 copy->type = info->type;
89 copy->host = g_strdup(info->host);
90 copy->port = info->port;
91 copy->username = g_strdup(info->username);
92 copy->password = g_strdup(info->password);
94 return copy;
97 void
98 purple_proxy_info_destroy(PurpleProxyInfo *info)
100 g_return_if_fail(info != NULL);
102 g_free(info->host);
103 g_free(info->username);
104 g_free(info->password);
106 g_free(info);
109 void
110 purple_proxy_info_set_proxy_type(PurpleProxyInfo *info, PurpleProxyType type)
112 g_return_if_fail(info != NULL);
114 info->type = type;
117 void
118 purple_proxy_info_set_host(PurpleProxyInfo *info, const char *host)
120 g_return_if_fail(info != NULL);
122 g_free(info->host);
123 info->host = g_strdup(host);
126 void
127 purple_proxy_info_set_port(PurpleProxyInfo *info, int port)
129 g_return_if_fail(info != NULL);
131 info->port = port;
134 void
135 purple_proxy_info_set_username(PurpleProxyInfo *info, const char *username)
137 g_return_if_fail(info != NULL);
139 g_free(info->username);
140 info->username = g_strdup(username);
143 void
144 purple_proxy_info_set_password(PurpleProxyInfo *info, const char *password)
146 g_return_if_fail(info != NULL);
148 g_free(info->password);
149 info->password = g_strdup(password);
152 PurpleProxyType
153 purple_proxy_info_get_proxy_type(const PurpleProxyInfo *info)
155 g_return_val_if_fail(info != NULL, PURPLE_PROXY_NONE);
157 return info->type;
160 const char *
161 purple_proxy_info_get_host(const PurpleProxyInfo *info)
163 g_return_val_if_fail(info != NULL, NULL);
165 return info->host;
169 purple_proxy_info_get_port(const PurpleProxyInfo *info)
171 g_return_val_if_fail(info != NULL, 0);
173 return info->port;
176 const char *
177 purple_proxy_info_get_username(const PurpleProxyInfo *info)
179 g_return_val_if_fail(info != NULL, NULL);
181 return info->username;
184 const char *
185 purple_proxy_info_get_password(const PurpleProxyInfo *info)
187 g_return_val_if_fail(info != NULL, NULL);
189 return info->password;
192 G_DEFINE_BOXED_TYPE(PurpleProxyInfo, purple_proxy_info,
193 purple_proxy_info_copy, purple_proxy_info_destroy);
195 /**************************************************************************
196 * Global Proxy API
197 **************************************************************************/
198 PurpleProxyInfo *
199 purple_global_proxy_get_info(void)
201 return global_proxy_info;
204 void
205 purple_global_proxy_set_info(PurpleProxyInfo *info)
207 g_return_if_fail(info != NULL);
209 purple_proxy_info_destroy(global_proxy_info);
211 global_proxy_info = info;
215 /* index in gproxycmds below, keep them in sync */
216 #define GNOME_PROXY_MODE 0
217 #define GNOME_PROXY_USE_SAME_PROXY 1
218 #define GNOME_PROXY_SOCKS_HOST 2
219 #define GNOME_PROXY_SOCKS_PORT 3
220 #define GNOME_PROXY_HTTP_HOST 4
221 #define GNOME_PROXY_HTTP_PORT 5
222 #define GNOME_PROXY_HTTP_USER 6
223 #define GNOME_PROXY_HTTP_PASS 7
224 #define GNOME2_CMDS 0
225 #define GNOME3_CMDS 1
227 /* detect proxy settings for gnome2/gnome3 */
228 static const char* gproxycmds[][2] = {
229 { "gconftool-2 -g /system/proxy/mode" , "gsettings get org.gnome.system.proxy mode" },
230 { "gconftool-2 -g /system/http_proxy/use_same_proxy", "gsettings get org.gnome.system.proxy use-same-proxy" },
231 { "gconftool-2 -g /system/proxy/socks_host", "gsettings get org.gnome.system.proxy.socks host" },
232 { "gconftool-2 -g /system/proxy/socks_port", "gsettings get org.gnome.system.proxy.socks port" },
233 { "gconftool-2 -g /system/http_proxy/host", "gsettings get org.gnome.system.proxy.http host" },
234 { "gconftool-2 -g /system/http_proxy/port", "gsettings get org.gnome.system.proxy.http port"},
235 { "gconftool-2 -g /system/http_proxy/authentication_user", "gsettings get org.gnome.system.proxy.http authentication-user" },
236 { "gconftool-2 -g /system/http_proxy/authentication_password", "gsettings get org.gnome.system.proxy.http authentication-password" },
240 * purple_gnome_proxy_get_parameter:
241 * @parameter: One of the GNOME_PROXY_x constants defined above
242 * @gnome_version: GNOME2_CMDS or GNOME3_CMDS
244 * This is a utility function used to retrieve proxy parameter values from
245 * GNOME 2/3 environment.
247 * Returns: The value of requested proxy parameter
249 static char *
250 purple_gnome_proxy_get_parameter(guint8 parameter, guint8 gnome_version)
252 gchar *param, *err;
253 size_t param_len;
255 if (parameter > GNOME_PROXY_HTTP_PASS)
256 return NULL;
257 if (gnome_version > GNOME3_CMDS)
258 return NULL;
260 if (!g_spawn_command_line_sync(gproxycmds[parameter][gnome_version],
261 &param, &err, NULL, NULL))
262 return NULL;
263 g_free(err);
265 g_strstrip(param);
266 if (param[0] == '\'' || param[0] == '\"') {
267 param_len = strlen(param);
268 memmove(param, param + 1, param_len); /* copy last \0 too */
269 --param_len;
270 if (param_len > 0 && (param[param_len - 1] == '\'' || param[param_len - 1] == '\"'))
271 param[param_len - 1] = '\0';
272 g_strstrip(param);
275 return param;
278 static PurpleProxyInfo *
279 purple_gnome_proxy_get_info(void)
281 static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
282 gboolean use_same_proxy = FALSE;
283 gchar *tmp;
284 guint8 gnome_version = GNOME3_CMDS;
286 tmp = g_find_program_in_path("gsettings");
287 if (tmp == NULL) {
288 tmp = g_find_program_in_path("gconftool-2");
289 gnome_version = GNOME2_CMDS;
291 if (tmp == NULL)
292 return purple_global_proxy_get_info();
294 g_free(tmp);
296 /* Check whether to use a proxy. */
297 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_MODE, gnome_version);
298 if (!tmp)
299 return purple_global_proxy_get_info();
301 if (purple_strequal(tmp, "none")) {
302 info.type = PURPLE_PROXY_NONE;
303 g_free(tmp);
304 return &info;
307 if (!purple_strequal(tmp, "manual")) {
308 /* Unknown setting. Fallback to using our global proxy settings. */
309 g_free(tmp);
310 return purple_global_proxy_get_info();
313 g_free(tmp);
315 /* Free the old fields */
316 g_free(info.host);
317 info.host = NULL;
318 g_free(info.username);
319 info.username = NULL;
320 g_free(info.password);
321 info.password = NULL;
323 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_USE_SAME_PROXY, gnome_version);
324 if (!tmp)
325 return purple_global_proxy_get_info();
327 if (purple_strequal(tmp, "true"))
328 use_same_proxy = TRUE;
330 g_free(tmp);
332 if (!use_same_proxy) {
333 info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_HOST, gnome_version);
334 if (!info.host)
335 return purple_global_proxy_get_info();
338 if (!use_same_proxy && (info.host != NULL) && (*info.host != '\0')) {
339 info.type = PURPLE_PROXY_SOCKS5;
340 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_PORT, gnome_version);
341 if (!tmp) {
342 g_free(info.host);
343 info.host = NULL;
344 return purple_global_proxy_get_info();
346 info.port = atoi(tmp);
347 g_free(tmp);
348 } else {
349 g_free(info.host);
350 info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_HOST, gnome_version);
351 if (!info.host)
352 return purple_global_proxy_get_info();
354 /* If we get this far then we know we're using an HTTP proxy */
355 info.type = PURPLE_PROXY_HTTP;
357 if (*info.host == '\0')
359 purple_debug_info("proxy", "Gnome proxy settings are set to "
360 "'manual' but no suitable proxy server is specified. Using "
361 "Pidgin's proxy settings instead.\n");
362 g_free(info.host);
363 info.host = NULL;
364 return purple_global_proxy_get_info();
367 info.username = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_USER, gnome_version);
368 if (!info.username)
370 g_free(info.host);
371 info.host = NULL;
372 return purple_global_proxy_get_info();
375 info.password = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PASS, gnome_version);
376 if (!info.password)
378 g_free(info.host);
379 info.host = NULL;
380 g_free(info.username);
381 info.username = NULL;
382 return purple_global_proxy_get_info();
385 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PORT, gnome_version);
386 if (!tmp)
388 g_free(info.host);
389 info.host = NULL;
390 g_free(info.username);
391 info.username = NULL;
392 g_free(info.password);
393 info.password = NULL;
394 return purple_global_proxy_get_info();
396 info.port = atoi(tmp);
397 g_free(tmp);
400 return &info;
403 #ifdef _WIN32
405 typedef BOOL (CALLBACK* LPFNWINHTTPGETIEPROXYCONFIG)(/*IN OUT*/ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* pProxyConfig);
407 /* This modifies "host" in-place evilly */
408 static void
409 _proxy_fill_hostinfo(PurpleProxyInfo *info, char *host, int default_port)
411 int port = default_port;
412 char *d;
414 d = g_strrstr(host, ":");
415 if (d) {
416 *d = '\0';
418 d++;
419 if (*d)
420 sscanf(d, "%d", &port);
422 if (port == 0)
423 port = default_port;
426 purple_proxy_info_set_host(info, host);
427 purple_proxy_info_set_port(info, port);
430 static PurpleProxyInfo *
431 purple_win32_proxy_get_info(void)
433 static LPFNWINHTTPGETIEPROXYCONFIG MyWinHttpGetIEProxyConfig = NULL;
434 static gboolean loaded = FALSE;
435 static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
437 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_proxy_config;
439 if (!loaded) {
440 loaded = TRUE;
441 MyWinHttpGetIEProxyConfig = (LPFNWINHTTPGETIEPROXYCONFIG)
442 wpurple_find_and_loadproc("winhttp.dll", "WinHttpGetIEProxyConfigForCurrentUser");
443 if (!MyWinHttpGetIEProxyConfig)
444 purple_debug_warning("proxy", "Unable to read Windows Proxy Settings.\n");
447 if (!MyWinHttpGetIEProxyConfig)
448 return NULL;
450 ZeroMemory(&ie_proxy_config, sizeof(ie_proxy_config));
451 if (!MyWinHttpGetIEProxyConfig(&ie_proxy_config)) {
452 purple_debug_error("proxy", "Error reading Windows Proxy Settings(%lu).\n", GetLastError());
453 return NULL;
456 /* We can't do much if it is autodetect*/
457 if (ie_proxy_config.fAutoDetect) {
458 purple_debug_error("proxy", "Windows Proxy Settings set to autodetect (not supported).\n");
460 /* TODO: For 3.0.0 we'll revisit this (maybe)*/
462 return NULL;
464 } else if (ie_proxy_config.lpszProxy) {
465 gchar *proxy_list = g_utf16_to_utf8(ie_proxy_config.lpszProxy, -1,
466 NULL, NULL, NULL);
468 /* We can't do anything about the bypass list, as we don't have the url */
469 /* TODO: For 3.0.0 we'll revisit this*/
471 /* There are proxy settings for several protocols */
472 if (proxy_list && *proxy_list) {
473 char *specific = NULL, *tmp;
475 /* If there is only a global proxy, which means "HTTP" */
476 if (!strchr(proxy_list, ';') || (specific = g_strstr_len(proxy_list, -1, "http=")) != NULL) {
478 if (specific) {
479 specific += strlen("http=");
480 tmp = strchr(specific, ';');
481 if (tmp)
482 *tmp = '\0';
483 /* specific now points the proxy server (and port) */
484 } else
485 specific = proxy_list;
487 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_HTTP);
488 _proxy_fill_hostinfo(&info, specific, 80);
489 /* TODO: is there a way to set the username/password? */
490 purple_proxy_info_set_username(&info, NULL);
491 purple_proxy_info_set_password(&info, NULL);
493 purple_debug_info("proxy", "Windows Proxy Settings: HTTP proxy: '%s:%d'.\n",
494 purple_proxy_info_get_host(&info),
495 purple_proxy_info_get_port(&info));
497 } else if ((specific = g_strstr_len(proxy_list, -1, "socks=")) != NULL) {
499 specific += strlen("socks=");
500 tmp = strchr(specific, ';');
501 if (tmp)
502 *tmp = '\0';
503 /* specific now points the proxy server (and port) */
505 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_SOCKS5);
506 _proxy_fill_hostinfo(&info, specific, 1080);
507 /* TODO: is there a way to set the username/password? */
508 purple_proxy_info_set_username(&info, NULL);
509 purple_proxy_info_set_password(&info, NULL);
511 purple_debug_info("proxy", "Windows Proxy Settings: SOCKS5 proxy: '%s:%d'.\n",
512 purple_proxy_info_get_host(&info),
513 purple_proxy_info_get_port(&info));
515 } else {
517 purple_debug_info("proxy", "Windows Proxy Settings: No supported proxy specified.\n");
519 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_NONE);
524 /* TODO: Fix API to be able look at proxy bypass settings */
526 g_free(proxy_list);
527 } else {
528 purple_debug_info("proxy", "No Windows proxy set.\n");
529 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_NONE);
532 if (ie_proxy_config.lpszAutoConfigUrl)
533 GlobalFree(ie_proxy_config.lpszAutoConfigUrl);
534 if (ie_proxy_config.lpszProxy)
535 GlobalFree(ie_proxy_config.lpszProxy);
536 if (ie_proxy_config.lpszProxyBypass)
537 GlobalFree(ie_proxy_config.lpszProxyBypass);
539 return &info;
541 #endif
544 /**************************************************************************
545 * Proxy API
546 **************************************************************************/
549 * Whoever calls this needs to have called
550 * purple_proxy_connect_data_disconnect() beforehand.
552 static void
553 purple_proxy_connect_data_destroy(PurpleProxyConnectData *connect_data)
555 if (!PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data))
556 return;
558 handles = g_slist_remove(handles, connect_data);
560 if(G_IS_CANCELLABLE(connect_data->cancellable)) {
561 g_cancellable_cancel(connect_data->cancellable);
563 g_object_unref(G_OBJECT(connect_data->cancellable));
565 connect_data->cancellable = NULL;
568 g_free(connect_data->host);
569 g_free(connect_data);
573 * purple_proxy_connect_data_disconnect:
574 * @error_message: An error message explaining why the connection
575 * failed. This will be passed to the callback function
576 * specified in the call to purple_proxy_connect(). If the
577 * connection was successful then pass in null.
579 * Free all information dealing with a connection attempt and
580 * reset the connect_data to prepare for it to try to connect
581 * to another IP address.
583 * If an error message is passed in, then we know the connection
584 * attempt failed. If so, we call the callback with the given
585 * error message, then destroy the connect_data.
587 static void
588 purple_proxy_connect_data_disconnect(PurpleProxyConnectData *connect_data, const gchar *error_message)
590 if (connect_data->fd >= 0)
592 close(connect_data->fd);
593 connect_data->fd = -1;
596 if (error_message != NULL)
598 purple_debug_error("proxy", "Connection attempt failed: %s\n",
599 error_message);
601 /* Everything failed! Tell the originator of the request. */
602 connect_data->connect_cb(connect_data->data, -1, error_message);
603 purple_proxy_connect_data_destroy(connect_data);
607 static void
608 purple_proxy_connect_data_connected(PurpleProxyConnectData *connect_data)
610 purple_debug_info("proxy", "Connected to %s:%d.\n",
611 connect_data->host, connect_data->port);
613 connect_data->connect_cb(connect_data->data, connect_data->fd, NULL);
616 * We've passed the file descriptor to the protocol, so it's no longer
617 * our responsibility, and we should be careful not to free it when
618 * we destroy the connect_data.
620 connect_data->fd = -1;
622 purple_proxy_connect_data_disconnect(connect_data, NULL);
623 purple_proxy_connect_data_destroy(connect_data);
626 PurpleProxyInfo *
627 purple_proxy_get_setup(PurpleAccount *account)
629 PurpleProxyInfo *gpi = NULL;
630 const gchar *tmp;
632 /* This is used as a fallback so we don't overwrite the selected proxy type */
633 static PurpleProxyInfo *tmp_none_proxy_info = NULL;
634 if (!tmp_none_proxy_info) {
635 tmp_none_proxy_info = purple_proxy_info_new();
636 purple_proxy_info_set_proxy_type(tmp_none_proxy_info, PURPLE_PROXY_NONE);
639 if (account && purple_account_get_proxy_info(account) != NULL) {
640 gpi = purple_account_get_proxy_info(account);
641 if (purple_proxy_info_get_proxy_type(gpi) == PURPLE_PROXY_USE_GLOBAL)
642 gpi = NULL;
644 if (gpi == NULL) {
645 if (purple_running_gnome())
646 gpi = purple_gnome_proxy_get_info();
647 else
648 gpi = purple_global_proxy_get_info();
651 if (purple_proxy_info_get_proxy_type(gpi) == PURPLE_PROXY_USE_ENVVAR) {
652 if ((tmp = g_getenv("HTTP_PROXY")) != NULL ||
653 (tmp = g_getenv("http_proxy")) != NULL ||
654 (tmp = g_getenv("HTTPPROXY")) != NULL)
656 PurpleHttpURL *url;
658 /* http_proxy-format:
659 * export http_proxy="http://user:passwd@your.proxy.server:port/"
661 url = purple_http_url_parse(tmp);
662 if (!url) {
663 purple_debug_warning("proxy", "Couldn't parse URL\n");
664 return gpi;
667 purple_proxy_info_set_host(gpi, purple_http_url_get_host(url));
668 purple_proxy_info_set_username(gpi, purple_http_url_get_username(url));
669 purple_proxy_info_set_password(gpi, purple_http_url_get_password(url));
670 purple_proxy_info_set_port(gpi, purple_http_url_get_port(url));
672 /* XXX: Do we want to skip this step if user/password/port were part of url? */
673 if ((tmp = g_getenv("HTTP_PROXY_USER")) != NULL ||
674 (tmp = g_getenv("http_proxy_user")) != NULL ||
675 (tmp = g_getenv("HTTPPROXYUSER")) != NULL)
676 purple_proxy_info_set_username(gpi, tmp);
678 if ((tmp = g_getenv("HTTP_PROXY_PASS")) != NULL ||
679 (tmp = g_getenv("http_proxy_pass")) != NULL ||
680 (tmp = g_getenv("HTTPPROXYPASS")) != NULL)
681 purple_proxy_info_set_password(gpi, tmp);
683 if ((tmp = g_getenv("HTTP_PROXY_PORT")) != NULL ||
684 (tmp = g_getenv("http_proxy_port")) != NULL ||
685 (tmp = g_getenv("HTTPPROXYPORT")) != NULL)
686 purple_proxy_info_set_port(gpi, atoi(tmp));
687 } else {
688 #ifdef _WIN32
689 PurpleProxyInfo *wgpi;
690 if ((wgpi = purple_win32_proxy_get_info()) != NULL)
691 return wgpi;
692 #endif
693 /* no proxy environment variable found, don't use a proxy */
694 purple_debug_info("proxy", "No environment settings found, not using a proxy\n");
695 gpi = tmp_none_proxy_info;
700 return gpi;
703 /* Grabbed duplicate_fd() from GLib's testcases (gio/tests/socket.c).
704 * Can be dropped once this API has been converted to Gio.
706 static int
707 duplicate_fd (int fd)
709 #ifdef G_OS_WIN32
710 HANDLE newfd;
712 if (!DuplicateHandle (GetCurrentProcess (),
713 (HANDLE)fd,
714 GetCurrentProcess (),
715 &newfd,
717 FALSE,
718 DUPLICATE_SAME_ACCESS))
720 return -1;
723 return (int)newfd;
724 #else
725 return dup (fd);
726 #endif
728 /* End function grabbed from GLib */
730 static void
731 connect_to_host_cb(GObject *source, GAsyncResult *res, gpointer user_data)
733 PurpleProxyConnectData *connect_data = user_data;
734 GSocketConnection *conn;
735 GError *error = NULL;
736 GSocket *socket;
738 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
739 res, &error);
740 if (conn == NULL) {
741 /* Ignore cancelled error as that signifies connect_data has
742 * been freed
744 if (!g_error_matches(error, G_IO_ERROR,
745 G_IO_ERROR_CANCELLED)) {
746 purple_debug_error("proxy", "Unable to connect to "
747 "destination host: %s\n",
748 error->message);
749 purple_proxy_connect_data_disconnect(connect_data,
750 "Unable to connect to destination "
751 "host.\n");
754 g_clear_error(&error);
755 return;
758 socket = g_socket_connection_get_socket(conn);
759 g_assert(socket != NULL);
761 /* Duplicate the file descriptor, and then free the connection.
762 * libpurple's proxy code doesn't keep an object around for the
763 * lifetime of the connection. Therefore, in order to not leak
764 * memory, the GSocketConnection must be freed here. In order
765 * to avoid the double close/free of the file descriptor, the
766 * file descriptor is duplicated.
768 connect_data->fd = duplicate_fd(g_socket_get_fd(socket));
769 g_object_unref(conn);
771 purple_proxy_connect_data_connected(connect_data);
774 PurpleProxyConnectData *
775 purple_proxy_connect(void *handle, PurpleAccount *account,
776 const char *host, int port,
777 PurpleProxyConnectFunction connect_cb, gpointer data)
779 PurpleProxyConnectData *connect_data;
780 GSocketClient *client;
781 GError *error = NULL;
783 g_return_val_if_fail(host != NULL, NULL);
784 g_return_val_if_fail(port > 0, NULL);
785 g_return_val_if_fail(connect_cb != NULL, NULL);
787 client = purple_gio_socket_client_new(account, &error);
789 if (client == NULL) {
790 /* Assume it's a proxy error */
791 purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
792 error->message,
793 purple_request_cpar_from_account(account));
794 g_clear_error(&error);
795 return NULL;
798 connect_data = g_new0(PurpleProxyConnectData, 1);
799 connect_data->fd = -1;
800 connect_data->handle = handle;
801 connect_data->connect_cb = connect_cb;
802 connect_data->data = data;
803 connect_data->host = g_strdup(host);
804 connect_data->port = port;
805 connect_data->gpi = purple_proxy_get_setup(account);
806 connect_data->cancellable = g_cancellable_new();
808 purple_debug_info("proxy", "Attempting connection to %s:%u\n",
809 host, port);
811 g_socket_client_connect_to_host_async(client, host, port,
812 connect_data->cancellable, connect_to_host_cb,
813 connect_data);
814 g_object_unref(client);
816 handles = g_slist_prepend(handles, connect_data);
818 return connect_data;
821 static void
822 socks5_proxy_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
824 PurpleProxyConnectData *connect_data = user_data;
825 GIOStream *stream;
826 GError *error = NULL;
827 GSocket *socket;
829 stream = g_proxy_connect_finish(G_PROXY(source), res, &error);
831 if (stream == NULL) {
832 /* Ignore cancelled error as that signifies connect_data has
833 * been freed
835 if (!g_error_matches(error, G_IO_ERROR,
836 G_IO_ERROR_CANCELLED)) {
837 purple_debug_error("proxy", "Unable to connect to "
838 "destination host: %s\n",
839 error->message);
840 purple_proxy_connect_data_disconnect(connect_data,
841 "Unable to connect to destination "
842 "host.\n");
845 g_clear_error(&error);
846 return;
849 if (!G_IS_SOCKET_CONNECTION(stream)) {
850 purple_debug_error("proxy",
851 "GProxy didn't return a GSocketConnection.\n");
852 purple_proxy_connect_data_disconnect(connect_data,
853 "GProxy didn't return a GSocketConnection.\n");
854 g_object_unref(stream);
855 return;
858 socket = g_socket_connection_get_socket(G_SOCKET_CONNECTION(stream));
860 /* Duplicate the file descriptor, and then free the connection.
861 * libpurple's proxy code doesn't keep an object around for the
862 * lifetime of the connection. Therefore, in order to not leak
863 * memory, the GSocketConnection (aka GIOStream here) must be
864 * freed here. In order to avoid the double close/free of the
865 * file descriptor, the file descriptor is duplicated.
867 connect_data->fd = duplicate_fd(g_socket_get_fd(socket));
868 g_object_unref(stream);
870 purple_proxy_connect_data_connected(connect_data);
873 /* This is called when we connect to the SOCKS5 proxy server (through any
874 * relevant account proxy)
876 static void
877 socks5_connect_to_host_cb(GObject *source, GAsyncResult *res,
878 gpointer user_data)
880 PurpleProxyConnectData *connect_data = user_data;
881 GSocketConnection *conn;
882 GError *error = NULL;
883 GProxy *proxy;
884 PurpleProxyInfo *info;
885 GSocketAddress *addr;
886 GInetSocketAddress *inet_addr;
887 GSocketAddress *proxy_addr;
889 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
890 res, &error);
891 if (conn == NULL) {
892 /* Ignore cancelled error as that signifies connect_data has
893 * been freed
895 if (!g_error_matches(error, G_IO_ERROR,
896 G_IO_ERROR_CANCELLED)) {
897 purple_debug_error("proxy", "Unable to connect to "
898 "SOCKS5 host: %s\n", error->message);
899 purple_proxy_connect_data_disconnect(connect_data,
900 "Unable to connect to SOCKS5 host.\n");
903 g_clear_error(&error);
904 return;
907 proxy = g_proxy_get_default_for_protocol("socks5");
908 if (proxy == NULL) {
909 purple_debug_error("proxy", "SOCKS5 proxy backend missing.\n");
910 purple_proxy_connect_data_disconnect(connect_data,
911 "SOCKS5 proxy backend missing.\n");
912 g_object_unref(conn);
913 return;
916 info = connect_data->gpi;
918 addr = g_socket_connection_get_remote_address(conn, &error);
919 if (addr == NULL) {
920 purple_debug_error("proxy", "Unable to retrieve SOCKS5 host "
921 "address from connection: %s\n",
922 error->message);
923 purple_proxy_connect_data_disconnect(connect_data,
924 "Unable to retrieve SOCKS5 host address from "
925 "connection");
926 g_object_unref(conn);
927 g_object_unref(proxy);
928 g_clear_error(&error);
929 return;
932 inet_addr = G_INET_SOCKET_ADDRESS(addr);
934 proxy_addr = g_proxy_address_new(
935 g_inet_socket_address_get_address(inet_addr),
936 g_inet_socket_address_get_port(inet_addr),
937 "socks5", connect_data->host, connect_data->port,
938 purple_proxy_info_get_username(info),
939 purple_proxy_info_get_password(info));
940 g_object_unref(inet_addr);
942 purple_debug_info("proxy", "Initiating SOCKS5 negotiation.\n");
944 purple_debug_info("proxy",
945 "Connecting to %s:%d via %s:%d using SOCKS5\n",
946 connect_data->host, connect_data->port,
947 purple_proxy_info_get_host(connect_data->gpi),
948 purple_proxy_info_get_port(connect_data->gpi));
950 g_proxy_connect_async(proxy, G_IO_STREAM(conn),
951 G_PROXY_ADDRESS(proxy_addr),
952 connect_data->cancellable,
953 socks5_proxy_connect_cb, connect_data);
954 g_object_unref(proxy_addr);
955 g_object_unref(conn);
956 g_object_unref(proxy);
960 * Combine some of this code with purple_proxy_connect()
962 PurpleProxyConnectData *
963 purple_proxy_connect_socks5_account(void *handle, PurpleAccount *account,
964 PurpleProxyInfo *gpi,
965 const char *host, int port,
966 PurpleProxyConnectFunction connect_cb,
967 gpointer data)
969 PurpleProxyConnectData *connect_data;
970 GSocketClient *client;
971 GError *error = NULL;
973 g_return_val_if_fail(host != NULL, NULL);
974 g_return_val_if_fail(port >= 0, NULL);
975 g_return_val_if_fail(connect_cb != NULL, NULL);
977 client = purple_gio_socket_client_new(account, &error);
979 if (client == NULL) {
980 /* Assume it's a proxy error */
981 purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
982 error->message,
983 purple_request_cpar_from_account(account));
984 g_clear_error(&error);
985 return NULL;
988 connect_data = g_new0(PurpleProxyConnectData, 1);
989 connect_data->fd = -1;
990 connect_data->handle = handle;
991 connect_data->connect_cb = connect_cb;
992 connect_data->data = data;
993 connect_data->host = g_strdup(host);
994 connect_data->port = port;
995 connect_data->gpi = gpi;
996 connect_data->cancellable = g_cancellable_new();
998 purple_debug_info("proxy",
999 "Connecting to %s:%d via %s:%d using SOCKS5\n",
1000 connect_data->host, connect_data->port,
1001 purple_proxy_info_get_host(connect_data->gpi),
1002 purple_proxy_info_get_port(connect_data->gpi));
1004 g_socket_client_connect_to_host_async(client,
1005 purple_proxy_info_get_host(connect_data->gpi),
1006 purple_proxy_info_get_port(connect_data->gpi),
1007 connect_data->cancellable, socks5_connect_to_host_cb,
1008 connect_data);
1009 g_object_unref(client);
1011 handles = g_slist_prepend(handles, connect_data);
1013 return connect_data;
1016 void
1017 purple_proxy_connect_cancel(PurpleProxyConnectData *connect_data)
1019 g_return_if_fail(connect_data != NULL);
1021 purple_proxy_connect_data_disconnect(connect_data, NULL);
1022 purple_proxy_connect_data_destroy(connect_data);
1025 void
1026 purple_proxy_connect_cancel_with_handle(void *handle)
1028 GSList *l, *l_next;
1030 for (l = handles; l != NULL; l = l_next) {
1031 PurpleProxyConnectData *connect_data = l->data;
1033 l_next = l->next;
1035 if (connect_data->handle == handle)
1036 purple_proxy_connect_cancel(connect_data);
1040 GProxyResolver *
1041 purple_proxy_get_proxy_resolver(PurpleAccount *account, GError **error)
1043 PurpleProxyInfo *info = purple_proxy_get_setup(account);
1044 const gchar *protocol;
1045 const gchar *username;
1046 const gchar *password;
1047 gchar *auth;
1048 gchar *proxy;
1049 GProxyResolver *resolver;
1051 if (purple_proxy_info_get_proxy_type(info) == PURPLE_PROXY_NONE) {
1052 /* Return an empty simple resolver, which will resolve on direct
1053 * connection. */
1054 return g_simple_proxy_resolver_new(NULL, NULL);
1057 switch (purple_proxy_info_get_proxy_type(info))
1059 /* PURPLE_PROXY_NONE already handled above */
1061 case PURPLE_PROXY_USE_ENVVAR:
1062 /* Intentional passthrough */
1063 case PURPLE_PROXY_HTTP:
1064 protocol = "http";
1065 break;
1066 case PURPLE_PROXY_SOCKS4:
1067 protocol = "socks4";
1068 break;
1069 case PURPLE_PROXY_SOCKS5:
1070 /* Intentional passthrough */
1071 case PURPLE_PROXY_TOR:
1072 protocol = "socks5";
1073 break;
1075 default:
1076 g_set_error(error, PURPLE_CONNECTION_ERROR,
1077 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1078 _("Invalid Proxy type (%d) specified"),
1079 purple_proxy_info_get_proxy_type(info));
1080 return NULL;
1084 if (purple_proxy_info_get_host(info) == NULL ||
1085 purple_proxy_info_get_port(info) <= 0) {
1086 g_set_error_literal(error, PURPLE_CONNECTION_ERROR,
1087 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1088 _("Either the host name or port number "
1089 "specified for your given proxy type is "
1090 "invalid."));
1091 return NULL;
1094 /* Everything checks out. Create and return the GProxyResolver */
1096 username = purple_proxy_info_get_username(info);
1097 password = purple_proxy_info_get_password(info);
1099 /* Username and password are optional */
1100 if (username != NULL && password != NULL) {
1101 auth = g_strdup_printf("%s:%s@", username, password);
1102 } else if (username != NULL) {
1103 auth = g_strdup_printf("%s@", username);
1104 } else {
1105 auth = NULL;
1108 proxy = g_strdup_printf("%s://%s%s:%i", protocol,
1109 auth != NULL ? auth : "",
1110 purple_proxy_info_get_host(info),
1111 purple_proxy_info_get_port(info));
1112 g_free(auth);
1114 resolver = g_simple_proxy_resolver_new(proxy, NULL);
1115 g_free(proxy);
1117 return resolver;
1120 static void
1121 proxy_pref_cb(const char *name, PurplePrefType type,
1122 gconstpointer value, gpointer data)
1124 PurpleProxyInfo *info = purple_global_proxy_get_info();
1126 if (purple_strequal(name, "/purple/proxy/type")) {
1127 int proxytype;
1128 const char *type = value;
1130 if (purple_strequal(type, "none"))
1131 proxytype = PURPLE_PROXY_NONE;
1132 else if (purple_strequal(type, "http"))
1133 proxytype = PURPLE_PROXY_HTTP;
1134 else if (purple_strequal(type, "socks4"))
1135 proxytype = PURPLE_PROXY_SOCKS4;
1136 else if (purple_strequal(type, "socks5"))
1137 proxytype = PURPLE_PROXY_SOCKS5;
1138 else if (purple_strequal(type, "tor"))
1139 proxytype = PURPLE_PROXY_TOR;
1140 else if (purple_strequal(type, "envvar"))
1141 proxytype = PURPLE_PROXY_USE_ENVVAR;
1142 else
1143 proxytype = -1;
1145 purple_proxy_info_set_proxy_type(info, proxytype);
1146 } else if (purple_strequal(name, "/purple/proxy/host"))
1147 purple_proxy_info_set_host(info, value);
1148 else if (purple_strequal(name, "/purple/proxy/port"))
1149 purple_proxy_info_set_port(info, GPOINTER_TO_INT(value));
1150 else if (purple_strequal(name, "/purple/proxy/username"))
1151 purple_proxy_info_set_username(info, value);
1152 else if (purple_strequal(name, "/purple/proxy/password"))
1153 purple_proxy_info_set_password(info, value);
1156 void *
1157 purple_proxy_get_handle()
1159 static int handle;
1161 return &handle;
1164 void
1165 purple_proxy_init(void)
1167 void *handle;
1169 /* Initialize a default proxy info struct. */
1170 global_proxy_info = purple_proxy_info_new();
1172 /* Proxy */
1173 purple_prefs_add_none("/purple/proxy");
1174 purple_prefs_add_string("/purple/proxy/type", "none");
1175 purple_prefs_add_string("/purple/proxy/host", "");
1176 purple_prefs_add_int("/purple/proxy/port", 0);
1177 purple_prefs_add_string("/purple/proxy/username", "");
1178 purple_prefs_add_string("/purple/proxy/password", "");
1179 purple_prefs_add_bool("/purple/proxy/socks4_remotedns", FALSE);
1181 /* Setup callbacks for the preferences. */
1182 handle = purple_proxy_get_handle();
1183 purple_prefs_connect_callback(handle, "/purple/proxy/type", proxy_pref_cb,
1184 NULL);
1185 purple_prefs_connect_callback(handle, "/purple/proxy/host", proxy_pref_cb,
1186 NULL);
1187 purple_prefs_connect_callback(handle, "/purple/proxy/port", proxy_pref_cb,
1188 NULL);
1189 purple_prefs_connect_callback(handle, "/purple/proxy/username",
1190 proxy_pref_cb, NULL);
1191 purple_prefs_connect_callback(handle, "/purple/proxy/password",
1192 proxy_pref_cb, NULL);
1194 /* Load the initial proxy settings */
1195 purple_prefs_trigger_callback("/purple/proxy/type");
1196 purple_prefs_trigger_callback("/purple/proxy/host");
1197 purple_prefs_trigger_callback("/purple/proxy/port");
1198 purple_prefs_trigger_callback("/purple/proxy/username");
1199 purple_prefs_trigger_callback("/purple/proxy/password");
1202 void
1203 purple_proxy_uninit(void)
1205 while (handles != NULL)
1207 purple_proxy_connect_data_disconnect(handles->data, NULL);
1208 purple_proxy_connect_data_destroy(handles->data);
1211 purple_prefs_disconnect_by_handle(purple_proxy_get_handle());
1213 purple_proxy_info_destroy(global_proxy_info);
1214 global_proxy_info = NULL;