Respect -c option
[pidgin-git.git] / libpurple / proxy.c
blob14471351913c1a89d49208adc4815cf51c07a1aa
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 "ciphers/md5hash.h"
31 #include "debug.h"
32 #include "http.h"
33 #include "notify.h"
34 #include "ntlm.h"
35 #include "prefs.h"
36 #include "proxy.h"
37 #include "purple-gio.h"
38 #include "util.h"
40 #include <gio/gio.h>
42 struct _PurpleProxyInfo
44 PurpleProxyType type; /* The proxy type. */
46 char *host; /* The host. */
47 int port; /* The port number. */
48 char *username; /* The username. */
49 char *password; /* The password. */
52 struct _PurpleProxyConnectData {
53 void *handle;
54 PurpleProxyConnectFunction connect_cb;
55 gpointer data;
56 gchar *host;
57 int port;
58 int fd;
59 PurpleProxyInfo *gpi;
61 GCancellable *cancellable;
64 static PurpleProxyInfo *global_proxy_info = NULL;
66 static GSList *handles = NULL;
69 * TODO: Eventually (GObjectification) this bad boy will be removed, because it is
70 * a gross fix for a crashy problem.
72 #define PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data) g_slist_find(handles, connect_data)
74 /**************************************************************************
75 * Proxy structure API
76 **************************************************************************/
77 PurpleProxyInfo *
78 purple_proxy_info_new(void)
80 return g_new0(PurpleProxyInfo, 1);
83 void
84 purple_proxy_info_destroy(PurpleProxyInfo *info)
86 g_return_if_fail(info != NULL);
88 g_free(info->host);
89 g_free(info->username);
90 g_free(info->password);
92 g_free(info);
95 void
96 purple_proxy_info_set_proxy_type(PurpleProxyInfo *info, PurpleProxyType type)
98 g_return_if_fail(info != NULL);
100 info->type = type;
103 void
104 purple_proxy_info_set_host(PurpleProxyInfo *info, const char *host)
106 g_return_if_fail(info != NULL);
108 g_free(info->host);
109 info->host = g_strdup(host);
112 void
113 purple_proxy_info_set_port(PurpleProxyInfo *info, int port)
115 g_return_if_fail(info != NULL);
117 info->port = port;
120 void
121 purple_proxy_info_set_username(PurpleProxyInfo *info, const char *username)
123 g_return_if_fail(info != NULL);
125 g_free(info->username);
126 info->username = g_strdup(username);
129 void
130 purple_proxy_info_set_password(PurpleProxyInfo *info, const char *password)
132 g_return_if_fail(info != NULL);
134 g_free(info->password);
135 info->password = g_strdup(password);
138 PurpleProxyType
139 purple_proxy_info_get_proxy_type(const PurpleProxyInfo *info)
141 g_return_val_if_fail(info != NULL, PURPLE_PROXY_NONE);
143 return info->type;
146 const char *
147 purple_proxy_info_get_host(const PurpleProxyInfo *info)
149 g_return_val_if_fail(info != NULL, NULL);
151 return info->host;
155 purple_proxy_info_get_port(const PurpleProxyInfo *info)
157 g_return_val_if_fail(info != NULL, 0);
159 return info->port;
162 const char *
163 purple_proxy_info_get_username(const PurpleProxyInfo *info)
165 g_return_val_if_fail(info != NULL, NULL);
167 return info->username;
170 const char *
171 purple_proxy_info_get_password(const PurpleProxyInfo *info)
173 g_return_val_if_fail(info != NULL, NULL);
175 return info->password;
178 /**************************************************************************
179 * Global Proxy API
180 **************************************************************************/
181 PurpleProxyInfo *
182 purple_global_proxy_get_info(void)
184 return global_proxy_info;
187 void
188 purple_global_proxy_set_info(PurpleProxyInfo *info)
190 g_return_if_fail(info != NULL);
192 purple_proxy_info_destroy(global_proxy_info);
194 global_proxy_info = info;
198 /* index in gproxycmds below, keep them in sync */
199 #define GNOME_PROXY_MODE 0
200 #define GNOME_PROXY_USE_SAME_PROXY 1
201 #define GNOME_PROXY_SOCKS_HOST 2
202 #define GNOME_PROXY_SOCKS_PORT 3
203 #define GNOME_PROXY_HTTP_HOST 4
204 #define GNOME_PROXY_HTTP_PORT 5
205 #define GNOME_PROXY_HTTP_USER 6
206 #define GNOME_PROXY_HTTP_PASS 7
207 #define GNOME2_CMDS 0
208 #define GNOME3_CMDS 1
210 /* detect proxy settings for gnome2/gnome3 */
211 static const char* gproxycmds[][2] = {
212 { "gconftool-2 -g /system/proxy/mode" , "gsettings get org.gnome.system.proxy mode" },
213 { "gconftool-2 -g /system/http_proxy/use_same_proxy", "gsettings get org.gnome.system.proxy use-same-proxy" },
214 { "gconftool-2 -g /system/proxy/socks_host", "gsettings get org.gnome.system.proxy.socks host" },
215 { "gconftool-2 -g /system/proxy/socks_port", "gsettings get org.gnome.system.proxy.socks port" },
216 { "gconftool-2 -g /system/http_proxy/host", "gsettings get org.gnome.system.proxy.http host" },
217 { "gconftool-2 -g /system/http_proxy/port", "gsettings get org.gnome.system.proxy.http port"},
218 { "gconftool-2 -g /system/http_proxy/authentication_user", "gsettings get org.gnome.system.proxy.http authentication-user" },
219 { "gconftool-2 -g /system/http_proxy/authentication_password", "gsettings get org.gnome.system.proxy.http authentication-password" },
223 * purple_gnome_proxy_get_parameter:
224 * @parameter: One of the GNOME_PROXY_x constants defined above
225 * @gnome_version: GNOME2_CMDS or GNOME3_CMDS
227 * This is a utility function used to retrieve proxy parameter values from
228 * GNOME 2/3 environment.
230 * Returns: The value of requested proxy parameter
232 static char *
233 purple_gnome_proxy_get_parameter(guint8 parameter, guint8 gnome_version)
235 gchar *param, *err;
236 size_t param_len;
238 if (parameter > GNOME_PROXY_HTTP_PASS)
239 return NULL;
240 if (gnome_version > GNOME3_CMDS)
241 return NULL;
243 if (!g_spawn_command_line_sync(gproxycmds[parameter][gnome_version],
244 &param, &err, NULL, NULL))
245 return NULL;
246 g_free(err);
248 g_strstrip(param);
249 if (param[0] == '\'' || param[0] == '\"') {
250 param_len = strlen(param);
251 memmove(param, param + 1, param_len); /* copy last \0 too */
252 --param_len;
253 if (param_len > 0 && (param[param_len - 1] == '\'' || param[param_len - 1] == '\"'))
254 param[param_len - 1] = '\0';
255 g_strstrip(param);
258 return param;
261 static PurpleProxyInfo *
262 purple_gnome_proxy_get_info(void)
264 static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
265 gboolean use_same_proxy = FALSE;
266 gchar *tmp;
267 guint8 gnome_version = GNOME3_CMDS;
269 tmp = g_find_program_in_path("gsettings");
270 if (tmp == NULL) {
271 tmp = g_find_program_in_path("gconftool-2");
272 gnome_version = GNOME2_CMDS;
274 if (tmp == NULL)
275 return purple_global_proxy_get_info();
277 g_free(tmp);
279 /* Check whether to use a proxy. */
280 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_MODE, gnome_version);
281 if (!tmp)
282 return purple_global_proxy_get_info();
284 if (purple_strequal(tmp, "none")) {
285 info.type = PURPLE_PROXY_NONE;
286 g_free(tmp);
287 return &info;
290 if (!purple_strequal(tmp, "manual")) {
291 /* Unknown setting. Fallback to using our global proxy settings. */
292 g_free(tmp);
293 return purple_global_proxy_get_info();
296 g_free(tmp);
298 /* Free the old fields */
299 g_free(info.host);
300 info.host = NULL;
301 g_free(info.username);
302 info.username = NULL;
303 g_free(info.password);
304 info.password = NULL;
306 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_USE_SAME_PROXY, gnome_version);
307 if (!tmp)
308 return purple_global_proxy_get_info();
310 if (purple_strequal(tmp, "true"))
311 use_same_proxy = TRUE;
313 g_free(tmp);
315 if (!use_same_proxy) {
316 info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_HOST, gnome_version);
317 if (!info.host)
318 return purple_global_proxy_get_info();
321 if (!use_same_proxy && (info.host != NULL) && (*info.host != '\0')) {
322 info.type = PURPLE_PROXY_SOCKS5;
323 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_SOCKS_PORT, gnome_version);
324 if (!tmp) {
325 g_free(info.host);
326 info.host = NULL;
327 return purple_global_proxy_get_info();
329 info.port = atoi(tmp);
330 g_free(tmp);
331 } else {
332 g_free(info.host);
333 info.host = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_HOST, gnome_version);
334 if (!info.host)
335 return purple_global_proxy_get_info();
337 /* If we get this far then we know we're using an HTTP proxy */
338 info.type = PURPLE_PROXY_HTTP;
340 if (*info.host == '\0')
342 purple_debug_info("proxy", "Gnome proxy settings are set to "
343 "'manual' but no suitable proxy server is specified. Using "
344 "Pidgin's proxy settings instead.\n");
345 g_free(info.host);
346 info.host = NULL;
347 return purple_global_proxy_get_info();
350 info.username = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_USER, gnome_version);
351 if (!info.username)
353 g_free(info.host);
354 info.host = NULL;
355 return purple_global_proxy_get_info();
358 info.password = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PASS, gnome_version);
359 if (!info.password)
361 g_free(info.host);
362 info.host = NULL;
363 g_free(info.username);
364 info.username = NULL;
365 return purple_global_proxy_get_info();
368 tmp = purple_gnome_proxy_get_parameter(GNOME_PROXY_HTTP_PORT, gnome_version);
369 if (!tmp)
371 g_free(info.host);
372 info.host = NULL;
373 g_free(info.username);
374 info.username = NULL;
375 g_free(info.password);
376 info.password = NULL;
377 return purple_global_proxy_get_info();
379 info.port = atoi(tmp);
380 g_free(tmp);
383 return &info;
386 #ifdef _WIN32
388 typedef BOOL (CALLBACK* LPFNWINHTTPGETIEPROXYCONFIG)(/*IN OUT*/ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* pProxyConfig);
390 /* This modifies "host" in-place evilly */
391 static void
392 _proxy_fill_hostinfo(PurpleProxyInfo *info, char *host, int default_port)
394 int port = default_port;
395 char *d;
397 d = g_strrstr(host, ":");
398 if (d) {
399 *d = '\0';
401 d++;
402 if (*d)
403 sscanf(d, "%d", &port);
405 if (port == 0)
406 port = default_port;
409 purple_proxy_info_set_host(info, host);
410 purple_proxy_info_set_port(info, port);
413 static PurpleProxyInfo *
414 purple_win32_proxy_get_info(void)
416 static LPFNWINHTTPGETIEPROXYCONFIG MyWinHttpGetIEProxyConfig = NULL;
417 static gboolean loaded = FALSE;
418 static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
420 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_proxy_config;
422 if (!loaded) {
423 loaded = TRUE;
424 MyWinHttpGetIEProxyConfig = (LPFNWINHTTPGETIEPROXYCONFIG)
425 wpurple_find_and_loadproc("winhttp.dll", "WinHttpGetIEProxyConfigForCurrentUser");
426 if (!MyWinHttpGetIEProxyConfig)
427 purple_debug_warning("proxy", "Unable to read Windows Proxy Settings.\n");
430 if (!MyWinHttpGetIEProxyConfig)
431 return NULL;
433 ZeroMemory(&ie_proxy_config, sizeof(ie_proxy_config));
434 if (!MyWinHttpGetIEProxyConfig(&ie_proxy_config)) {
435 purple_debug_error("proxy", "Error reading Windows Proxy Settings(%lu).\n", GetLastError());
436 return NULL;
439 /* We can't do much if it is autodetect*/
440 if (ie_proxy_config.fAutoDetect) {
441 purple_debug_error("proxy", "Windows Proxy Settings set to autodetect (not supported).\n");
443 /* TODO: For 3.0.0 we'll revisit this (maybe)*/
445 return NULL;
447 } else if (ie_proxy_config.lpszProxy) {
448 gchar *proxy_list = g_utf16_to_utf8(ie_proxy_config.lpszProxy, -1,
449 NULL, NULL, NULL);
451 /* We can't do anything about the bypass list, as we don't have the url */
452 /* TODO: For 3.0.0 we'll revisit this*/
454 /* There are proxy settings for several protocols */
455 if (proxy_list && *proxy_list) {
456 char *specific = NULL, *tmp;
458 /* If there is only a global proxy, which means "HTTP" */
459 if (!strchr(proxy_list, ';') || (specific = g_strstr_len(proxy_list, -1, "http=")) != NULL) {
461 if (specific) {
462 specific += strlen("http=");
463 tmp = strchr(specific, ';');
464 if (tmp)
465 *tmp = '\0';
466 /* specific now points the proxy server (and port) */
467 } else
468 specific = proxy_list;
470 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_HTTP);
471 _proxy_fill_hostinfo(&info, specific, 80);
472 /* TODO: is there a way to set the username/password? */
473 purple_proxy_info_set_username(&info, NULL);
474 purple_proxy_info_set_password(&info, NULL);
476 purple_debug_info("proxy", "Windows Proxy Settings: HTTP proxy: '%s:%d'.\n",
477 purple_proxy_info_get_host(&info),
478 purple_proxy_info_get_port(&info));
480 } else if ((specific = g_strstr_len(proxy_list, -1, "socks=")) != NULL) {
482 specific += strlen("socks=");
483 tmp = strchr(specific, ';');
484 if (tmp)
485 *tmp = '\0';
486 /* specific now points the proxy server (and port) */
488 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_SOCKS5);
489 _proxy_fill_hostinfo(&info, specific, 1080);
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: SOCKS5 proxy: '%s:%d'.\n",
495 purple_proxy_info_get_host(&info),
496 purple_proxy_info_get_port(&info));
498 } else {
500 purple_debug_info("proxy", "Windows Proxy Settings: No supported proxy specified.\n");
502 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_NONE);
507 /* TODO: Fix API to be able look at proxy bypass settings */
509 g_free(proxy_list);
510 } else {
511 purple_debug_info("proxy", "No Windows proxy set.\n");
512 purple_proxy_info_set_proxy_type(&info, PURPLE_PROXY_NONE);
515 if (ie_proxy_config.lpszAutoConfigUrl)
516 GlobalFree(ie_proxy_config.lpszAutoConfigUrl);
517 if (ie_proxy_config.lpszProxy)
518 GlobalFree(ie_proxy_config.lpszProxy);
519 if (ie_proxy_config.lpszProxyBypass)
520 GlobalFree(ie_proxy_config.lpszProxyBypass);
522 return &info;
524 #endif
527 /**************************************************************************
528 * Proxy API
529 **************************************************************************/
532 * Whoever calls this needs to have called
533 * purple_proxy_connect_data_disconnect() beforehand.
535 static void
536 purple_proxy_connect_data_destroy(PurpleProxyConnectData *connect_data)
538 if (!PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data))
539 return;
541 handles = g_slist_remove(handles, connect_data);
543 if(G_IS_CANCELLABLE(connect_data->cancellable)) {
544 g_cancellable_cancel(connect_data->cancellable);
546 g_object_unref(G_OBJECT(connect_data->cancellable));
548 connect_data->cancellable = NULL;
551 g_free(connect_data->host);
552 g_free(connect_data);
556 * purple_proxy_connect_data_disconnect:
557 * @error_message: An error message explaining why the connection
558 * failed. This will be passed to the callback function
559 * specified in the call to purple_proxy_connect(). If the
560 * connection was successful then pass in null.
562 * Free all information dealing with a connection attempt and
563 * reset the connect_data to prepare for it to try to connect
564 * to another IP address.
566 * If an error message is passed in, then we know the connection
567 * attempt failed. If so, we call the callback with the given
568 * error message, then destroy the connect_data.
570 static void
571 purple_proxy_connect_data_disconnect(PurpleProxyConnectData *connect_data, const gchar *error_message)
573 if (connect_data->fd >= 0)
575 close(connect_data->fd);
576 connect_data->fd = -1;
579 if (error_message != NULL)
581 purple_debug_error("proxy", "Connection attempt failed: %s\n",
582 error_message);
584 /* Everything failed! Tell the originator of the request. */
585 connect_data->connect_cb(connect_data->data, -1, error_message);
586 purple_proxy_connect_data_destroy(connect_data);
590 static void
591 purple_proxy_connect_data_connected(PurpleProxyConnectData *connect_data)
593 purple_debug_info("proxy", "Connected to %s:%d.\n",
594 connect_data->host, connect_data->port);
596 connect_data->connect_cb(connect_data->data, connect_data->fd, NULL);
599 * We've passed the file descriptor to the protocol, so it's no longer
600 * our responsibility, and we should be careful not to free it when
601 * we destroy the connect_data.
603 connect_data->fd = -1;
605 purple_proxy_connect_data_disconnect(connect_data, NULL);
606 purple_proxy_connect_data_destroy(connect_data);
609 PurpleProxyInfo *
610 purple_proxy_get_setup(PurpleAccount *account)
612 PurpleProxyInfo *gpi = NULL;
613 const gchar *tmp;
615 /* This is used as a fallback so we don't overwrite the selected proxy type */
616 static PurpleProxyInfo *tmp_none_proxy_info = NULL;
617 if (!tmp_none_proxy_info) {
618 tmp_none_proxy_info = purple_proxy_info_new();
619 purple_proxy_info_set_proxy_type(tmp_none_proxy_info, PURPLE_PROXY_NONE);
622 if (account && purple_account_get_proxy_info(account) != NULL) {
623 gpi = purple_account_get_proxy_info(account);
624 if (purple_proxy_info_get_proxy_type(gpi) == PURPLE_PROXY_USE_GLOBAL)
625 gpi = NULL;
627 if (gpi == NULL) {
628 if (purple_running_gnome())
629 gpi = purple_gnome_proxy_get_info();
630 else
631 gpi = purple_global_proxy_get_info();
634 if (purple_proxy_info_get_proxy_type(gpi) == PURPLE_PROXY_USE_ENVVAR) {
635 if ((tmp = g_getenv("HTTP_PROXY")) != NULL ||
636 (tmp = g_getenv("http_proxy")) != NULL ||
637 (tmp = g_getenv("HTTPPROXY")) != NULL)
639 PurpleHttpURL *url;
641 /* http_proxy-format:
642 * export http_proxy="http://user:passwd@your.proxy.server:port/"
644 url = purple_http_url_parse(tmp);
645 if (!url) {
646 purple_debug_warning("proxy", "Couldn't parse URL\n");
647 return gpi;
650 purple_proxy_info_set_host(gpi, purple_http_url_get_host(url));
651 purple_proxy_info_set_username(gpi, purple_http_url_get_username(url));
652 purple_proxy_info_set_password(gpi, purple_http_url_get_password(url));
653 purple_proxy_info_set_port(gpi, purple_http_url_get_port(url));
655 /* XXX: Do we want to skip this step if user/password/port were part of url? */
656 if ((tmp = g_getenv("HTTP_PROXY_USER")) != NULL ||
657 (tmp = g_getenv("http_proxy_user")) != NULL ||
658 (tmp = g_getenv("HTTPPROXYUSER")) != NULL)
659 purple_proxy_info_set_username(gpi, tmp);
661 if ((tmp = g_getenv("HTTP_PROXY_PASS")) != NULL ||
662 (tmp = g_getenv("http_proxy_pass")) != NULL ||
663 (tmp = g_getenv("HTTPPROXYPASS")) != NULL)
664 purple_proxy_info_set_password(gpi, tmp);
666 if ((tmp = g_getenv("HTTP_PROXY_PORT")) != NULL ||
667 (tmp = g_getenv("http_proxy_port")) != NULL ||
668 (tmp = g_getenv("HTTPPROXYPORT")) != NULL)
669 purple_proxy_info_set_port(gpi, atoi(tmp));
670 } else {
671 #ifdef _WIN32
672 PurpleProxyInfo *wgpi;
673 if ((wgpi = purple_win32_proxy_get_info()) != NULL)
674 return wgpi;
675 #endif
676 /* no proxy environment variable found, don't use a proxy */
677 purple_debug_info("proxy", "No environment settings found, not using a proxy\n");
678 gpi = tmp_none_proxy_info;
683 return gpi;
686 /* Grabbed duplicate_fd() from GLib's testcases (gio/tests/socket.c).
687 * Can be dropped once this API has been converted to Gio.
689 static int
690 duplicate_fd (int fd)
692 #ifdef G_OS_WIN32
693 HANDLE newfd;
695 if (!DuplicateHandle (GetCurrentProcess (),
696 (HANDLE)fd,
697 GetCurrentProcess (),
698 &newfd,
700 FALSE,
701 DUPLICATE_SAME_ACCESS))
703 return -1;
706 return (int)newfd;
707 #else
708 return dup (fd);
709 #endif
711 /* End function grabbed from GLib */
713 static void
714 connect_to_host_cb(GObject *source, GAsyncResult *res, gpointer user_data)
716 PurpleProxyConnectData *connect_data = user_data;
717 GSocketConnection *conn;
718 GError *error = NULL;
719 GSocket *socket;
721 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
722 res, &error);
723 if (conn == NULL) {
724 /* Ignore cancelled error as that signifies connect_data has
725 * been freed
727 if (!g_error_matches(error, G_IO_ERROR,
728 G_IO_ERROR_CANCELLED)) {
729 purple_debug_error("proxy", "Unable to connect to "
730 "destination host: %s\n",
731 error->message);
732 purple_proxy_connect_data_disconnect(connect_data,
733 "Unable to connect to destination "
734 "host.\n");
737 g_clear_error(&error);
738 return;
741 socket = g_socket_connection_get_socket(conn);
742 g_assert(socket != NULL);
744 /* Duplicate the file descriptor, and then free the connection.
745 * libpurple's proxy code doesn't keep an object around for the
746 * lifetime of the connection. Therefore, in order to not leak
747 * memory, the GSocketConnection must be freed here. In order
748 * to avoid the double close/free of the file descriptor, the
749 * file descriptor is duplicated.
751 connect_data->fd = duplicate_fd(g_socket_get_fd(socket));
752 g_object_unref(conn);
754 purple_proxy_connect_data_connected(connect_data);
757 PurpleProxyConnectData *
758 purple_proxy_connect(void *handle, PurpleAccount *account,
759 const char *host, int port,
760 PurpleProxyConnectFunction connect_cb, gpointer data)
762 PurpleProxyConnectData *connect_data;
763 GSocketClient *client;
764 GError *error = NULL;
766 g_return_val_if_fail(host != NULL, NULL);
767 g_return_val_if_fail(port > 0, NULL);
768 g_return_val_if_fail(connect_cb != NULL, NULL);
770 connect_data = g_new0(PurpleProxyConnectData, 1);
771 connect_data->fd = -1;
772 connect_data->handle = handle;
773 connect_data->connect_cb = connect_cb;
774 connect_data->data = data;
775 connect_data->host = g_strdup(host);
776 connect_data->port = port;
777 connect_data->gpi = purple_proxy_get_setup(account);
779 client = purple_gio_socket_client_new(account, &error);
781 if (client == NULL) {
782 /* Assume it's a proxy error */
783 purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
784 error->message,
785 purple_request_cpar_from_account(account));
786 g_clear_error(&error);
788 purple_proxy_connect_data_destroy(connect_data);
789 return NULL;
792 connect_data->cancellable = g_cancellable_new();
794 purple_debug_info("proxy", "Attempting connection to %s:%u\n",
795 host, port);
797 g_socket_client_connect_to_host_async(client, host, port,
798 connect_data->cancellable, connect_to_host_cb,
799 connect_data);
800 g_object_unref(client);
802 handles = g_slist_prepend(handles, connect_data);
804 return connect_data;
807 static void
808 socks5_proxy_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
810 PurpleProxyConnectData *connect_data = user_data;
811 GIOStream *stream;
812 GError *error = NULL;
813 GSocket *socket;
815 stream = g_proxy_connect_finish(G_PROXY(source), res, &error);
817 if (stream == NULL) {
818 /* Ignore cancelled error as that signifies connect_data has
819 * been freed
821 if (!g_error_matches(error, G_IO_ERROR,
822 G_IO_ERROR_CANCELLED)) {
823 purple_debug_error("proxy", "Unable to connect to "
824 "destination host: %s\n",
825 error->message);
826 purple_proxy_connect_data_disconnect(connect_data,
827 "Unable to connect to destination "
828 "host.\n");
831 g_clear_error(&error);
832 return;
835 if (!G_IS_SOCKET_CONNECTION(stream)) {
836 purple_debug_error("proxy",
837 "GProxy didn't return a GSocketConnection.\n");
838 purple_proxy_connect_data_disconnect(connect_data,
839 "GProxy didn't return a GSocketConnection.\n");
840 g_object_unref(stream);
841 return;
844 socket = g_socket_connection_get_socket(G_SOCKET_CONNECTION(stream));
846 /* Duplicate the file descriptor, and then free the connection.
847 * libpurple's proxy code doesn't keep an object around for the
848 * lifetime of the connection. Therefore, in order to not leak
849 * memory, the GSocketConnection (aka GIOStream here) must be
850 * freed here. In order to avoid the double close/free of the
851 * file descriptor, the file descriptor is duplicated.
853 connect_data->fd = duplicate_fd(g_socket_get_fd(socket));
854 g_object_unref(stream);
856 purple_proxy_connect_data_connected(connect_data);
859 /* This is called when we connect to the SOCKS5 proxy server (through any
860 * relevant account proxy)
862 static void
863 socks5_connect_to_host_cb(GObject *source, GAsyncResult *res,
864 gpointer user_data)
866 PurpleProxyConnectData *connect_data = user_data;
867 GSocketConnection *conn;
868 GError *error = NULL;
869 GProxy *proxy;
870 PurpleProxyInfo *info;
871 GSocketAddress *addr;
872 GInetSocketAddress *inet_addr;
873 GSocketAddress *proxy_addr;
875 conn = g_socket_client_connect_to_host_finish(G_SOCKET_CLIENT(source),
876 res, &error);
877 if (conn == NULL) {
878 /* Ignore cancelled error as that signifies connect_data has
879 * been freed
881 if (!g_error_matches(error, G_IO_ERROR,
882 G_IO_ERROR_CANCELLED)) {
883 purple_debug_error("proxy", "Unable to connect to "
884 "SOCKS5 host: %s\n", error->message);
885 purple_proxy_connect_data_disconnect(connect_data,
886 "Unable to connect to SOCKS5 host.\n");
889 g_clear_error(&error);
890 return;
893 proxy = g_proxy_get_default_for_protocol("socks5");
894 if (proxy == NULL) {
895 purple_debug_error("proxy", "SOCKS5 proxy backend missing.\n");
896 purple_proxy_connect_data_disconnect(connect_data,
897 "SOCKS5 proxy backend missing.\n");
898 g_object_unref(conn);
899 return;
902 info = connect_data->gpi;
904 addr = g_socket_connection_get_remote_address(conn, &error);
905 if (addr == NULL) {
906 purple_debug_error("proxy", "Unable to retrieve SOCKS5 host "
907 "address from connection: %s\n",
908 error->message);
909 purple_proxy_connect_data_disconnect(connect_data,
910 "Unable to retrieve SOCKS5 host address from "
911 "connection");
912 g_object_unref(conn);
913 g_object_unref(proxy);
914 g_clear_error(&error);
915 return;
918 inet_addr = G_INET_SOCKET_ADDRESS(addr);
920 proxy_addr = g_proxy_address_new(
921 g_inet_socket_address_get_address(inet_addr),
922 g_inet_socket_address_get_port(inet_addr),
923 "socks5", connect_data->host, connect_data->port,
924 purple_proxy_info_get_username(info),
925 purple_proxy_info_get_password(info));
926 g_object_unref(inet_addr);
928 purple_debug_info("proxy", "Initiating SOCKS5 negotiation.\n");
930 purple_debug_info("proxy",
931 "Connecting to %s:%d via %s:%d using SOCKS5\n",
932 connect_data->host, connect_data->port,
933 purple_proxy_info_get_host(connect_data->gpi),
934 purple_proxy_info_get_port(connect_data->gpi));
936 g_proxy_connect_async(proxy, G_IO_STREAM(conn),
937 G_PROXY_ADDRESS(proxy_addr),
938 connect_data->cancellable,
939 socks5_proxy_connect_cb, connect_data);
940 g_object_unref(proxy_addr);
941 g_object_unref(conn);
942 g_object_unref(proxy);
946 * Combine some of this code with purple_proxy_connect()
948 PurpleProxyConnectData *
949 purple_proxy_connect_socks5_account(void *handle, PurpleAccount *account,
950 PurpleProxyInfo *gpi,
951 const char *host, int port,
952 PurpleProxyConnectFunction connect_cb,
953 gpointer data)
955 PurpleProxyConnectData *connect_data;
956 GSocketClient *client;
957 GError *error = NULL;
959 g_return_val_if_fail(host != NULL, NULL);
960 g_return_val_if_fail(port >= 0, NULL);
961 g_return_val_if_fail(connect_cb != NULL, NULL);
963 connect_data = g_new0(PurpleProxyConnectData, 1);
964 connect_data->fd = -1;
965 connect_data->handle = handle;
966 connect_data->connect_cb = connect_cb;
967 connect_data->data = data;
968 connect_data->host = g_strdup(host);
969 connect_data->port = port;
970 connect_data->gpi = gpi;
972 client = purple_gio_socket_client_new(account, &error);
974 if (client == NULL) {
975 /* Assume it's a proxy error */
976 purple_notify_error(NULL, NULL, _("Invalid proxy settings"),
977 error->message,
978 purple_request_cpar_from_account(account));
979 g_clear_error(&error);
981 purple_proxy_connect_data_destroy(connect_data);
982 return NULL;
985 connect_data->cancellable = g_cancellable_new();
987 purple_debug_info("proxy",
988 "Connecting to %s:%d via %s:%d using SOCKS5\n",
989 connect_data->host, connect_data->port,
990 purple_proxy_info_get_host(connect_data->gpi),
991 purple_proxy_info_get_port(connect_data->gpi));
993 g_socket_client_connect_to_host_async(client,
994 purple_proxy_info_get_host(connect_data->gpi),
995 purple_proxy_info_get_port(connect_data->gpi),
996 connect_data->cancellable, socks5_connect_to_host_cb,
997 connect_data);
998 g_object_unref(client);
1000 handles = g_slist_prepend(handles, connect_data);
1002 return connect_data;
1005 void
1006 purple_proxy_connect_cancel(PurpleProxyConnectData *connect_data)
1008 g_return_if_fail(connect_data != NULL);
1010 purple_proxy_connect_data_disconnect(connect_data, NULL);
1011 purple_proxy_connect_data_destroy(connect_data);
1014 void
1015 purple_proxy_connect_cancel_with_handle(void *handle)
1017 GSList *l, *l_next;
1019 for (l = handles; l != NULL; l = l_next) {
1020 PurpleProxyConnectData *connect_data = l->data;
1022 l_next = l->next;
1024 if (connect_data->handle == handle)
1025 purple_proxy_connect_cancel(connect_data);
1029 GProxyResolver *
1030 purple_proxy_get_proxy_resolver(PurpleAccount *account, GError **error)
1032 PurpleProxyInfo *info = purple_proxy_get_setup(account);
1033 const gchar *protocol;
1034 const gchar *username;
1035 const gchar *password;
1036 gchar *auth;
1037 gchar *proxy;
1038 GProxyResolver *resolver;
1040 if (purple_proxy_info_get_proxy_type(info) == PURPLE_PROXY_NONE) {
1041 /* Return an empty simple resolver, which will resolve on direct
1042 * connection. */
1043 return g_simple_proxy_resolver_new(NULL, NULL);
1046 switch (purple_proxy_info_get_proxy_type(info))
1048 /* PURPLE_PROXY_NONE already handled above */
1050 case PURPLE_PROXY_USE_ENVVAR:
1051 /* Intentional passthrough */
1052 case PURPLE_PROXY_HTTP:
1053 protocol = "http";
1054 break;
1055 case PURPLE_PROXY_SOCKS4:
1056 protocol = "socks4";
1057 break;
1058 case PURPLE_PROXY_SOCKS5:
1059 /* Intentional passthrough */
1060 case PURPLE_PROXY_TOR:
1061 protocol = "socks5";
1062 break;
1064 default:
1065 g_set_error(error, PURPLE_CONNECTION_ERROR,
1066 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1067 _("Invalid Proxy type (%d) specified"),
1068 purple_proxy_info_get_proxy_type(info));
1069 return NULL;
1073 if (purple_proxy_info_get_host(info) == NULL ||
1074 purple_proxy_info_get_port(info) <= 0) {
1075 g_set_error_literal(error, PURPLE_CONNECTION_ERROR,
1076 PURPLE_CONNECTION_ERROR_INVALID_SETTINGS,
1077 _("Either the host name or port number "
1078 "specified for your given proxy type is "
1079 "invalid."));
1080 return NULL;
1083 /* Everything checks out. Create and return the GProxyResolver */
1085 username = purple_proxy_info_get_username(info);
1086 password = purple_proxy_info_get_password(info);
1088 /* Username and password are optional */
1089 if (username != NULL && password != NULL) {
1090 auth = g_strdup_printf("%s:%s@", username, password);
1091 } else if (username != NULL) {
1092 auth = g_strdup_printf("%s@", username);
1093 } else {
1094 auth = NULL;
1097 proxy = g_strdup_printf("%s://%s%s:%i", protocol,
1098 auth != NULL ? auth : "",
1099 purple_proxy_info_get_host(info),
1100 purple_proxy_info_get_port(info));
1101 g_free(auth);
1103 resolver = g_simple_proxy_resolver_new(proxy, NULL);
1104 g_free(proxy);
1106 return resolver;
1109 static void
1110 proxy_pref_cb(const char *name, PurplePrefType type,
1111 gconstpointer value, gpointer data)
1113 PurpleProxyInfo *info = purple_global_proxy_get_info();
1115 if (purple_strequal(name, "/purple/proxy/type")) {
1116 int proxytype;
1117 const char *type = value;
1119 if (purple_strequal(type, "none"))
1120 proxytype = PURPLE_PROXY_NONE;
1121 else if (purple_strequal(type, "http"))
1122 proxytype = PURPLE_PROXY_HTTP;
1123 else if (purple_strequal(type, "socks4"))
1124 proxytype = PURPLE_PROXY_SOCKS4;
1125 else if (purple_strequal(type, "socks5"))
1126 proxytype = PURPLE_PROXY_SOCKS5;
1127 else if (purple_strequal(type, "tor"))
1128 proxytype = PURPLE_PROXY_TOR;
1129 else if (purple_strequal(type, "envvar"))
1130 proxytype = PURPLE_PROXY_USE_ENVVAR;
1131 else
1132 proxytype = -1;
1134 purple_proxy_info_set_proxy_type(info, proxytype);
1135 } else if (purple_strequal(name, "/purple/proxy/host"))
1136 purple_proxy_info_set_host(info, value);
1137 else if (purple_strequal(name, "/purple/proxy/port"))
1138 purple_proxy_info_set_port(info, GPOINTER_TO_INT(value));
1139 else if (purple_strequal(name, "/purple/proxy/username"))
1140 purple_proxy_info_set_username(info, value);
1141 else if (purple_strequal(name, "/purple/proxy/password"))
1142 purple_proxy_info_set_password(info, value);
1145 void *
1146 purple_proxy_get_handle()
1148 static int handle;
1150 return &handle;
1153 void
1154 purple_proxy_init(void)
1156 void *handle;
1158 /* Initialize a default proxy info struct. */
1159 global_proxy_info = purple_proxy_info_new();
1161 /* Proxy */
1162 purple_prefs_add_none("/purple/proxy");
1163 purple_prefs_add_string("/purple/proxy/type", "none");
1164 purple_prefs_add_string("/purple/proxy/host", "");
1165 purple_prefs_add_int("/purple/proxy/port", 0);
1166 purple_prefs_add_string("/purple/proxy/username", "");
1167 purple_prefs_add_string("/purple/proxy/password", "");
1168 purple_prefs_add_bool("/purple/proxy/socks4_remotedns", FALSE);
1170 /* Setup callbacks for the preferences. */
1171 handle = purple_proxy_get_handle();
1172 purple_prefs_connect_callback(handle, "/purple/proxy/type", proxy_pref_cb,
1173 NULL);
1174 purple_prefs_connect_callback(handle, "/purple/proxy/host", proxy_pref_cb,
1175 NULL);
1176 purple_prefs_connect_callback(handle, "/purple/proxy/port", proxy_pref_cb,
1177 NULL);
1178 purple_prefs_connect_callback(handle, "/purple/proxy/username",
1179 proxy_pref_cb, NULL);
1180 purple_prefs_connect_callback(handle, "/purple/proxy/password",
1181 proxy_pref_cb, NULL);
1183 /* Load the initial proxy settings */
1184 purple_prefs_trigger_callback("/purple/proxy/type");
1185 purple_prefs_trigger_callback("/purple/proxy/host");
1186 purple_prefs_trigger_callback("/purple/proxy/port");
1187 purple_prefs_trigger_callback("/purple/proxy/username");
1188 purple_prefs_trigger_callback("/purple/proxy/password");
1191 void
1192 purple_proxy_uninit(void)
1194 while (handles != NULL)
1196 purple_proxy_connect_data_disconnect(handles->data, NULL);
1197 purple_proxy_connect_data_destroy(handles->data);
1200 purple_prefs_disconnect_by_handle(purple_proxy_get_handle());
1202 purple_proxy_info_destroy(global_proxy_info);
1203 global_proxy_info = NULL;