merge of 'd03c32ee4bd5625c28af8c8b33920944d499eef6'
[pidgin-git.git] / libpurple / proxy.c
blob111c1896bf5d07df93be5088e4e6929d484170ad
1 /**
2 * @file proxy.c Proxy API
3 * @ingroup core
4 */
6 /* purple
8 * Purple is the legal property of its developers, whose names are too numerous
9 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * source distribution.
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 /* this is a little piece of code to handle proxy connection */
29 /* it is intended to : 1st handle http proxy, using the CONNECT command
30 , 2nd provide an easy way to add socks support
31 , 3rd draw women to it like flies to honey */
33 #include "internal.h"
34 #include "cipher.h"
35 #include "debug.h"
36 #include "dnsquery.h"
37 #include "notify.h"
38 #include "ntlm.h"
39 #include "prefs.h"
40 #include "proxy.h"
41 #include "util.h"
43 struct _PurpleProxyConnectData {
44 void *handle;
45 PurpleProxyConnectFunction connect_cb;
46 gpointer data;
47 gchar *host;
48 int port;
49 int fd;
50 int socket_type;
51 guint inpa;
52 PurpleProxyInfo *gpi;
53 PurpleDnsQueryData *query_data;
55 /**
56 * This contains alternating length/char* values. The char*
57 * values need to be freed when removed from the linked list.
59 GSList *hosts;
62 * All of the following variables are used when establishing a
63 * connection through a proxy.
65 guchar *write_buffer;
66 gsize write_buf_len;
67 gsize written_len;
68 PurpleInputFunction read_cb;
69 guchar *read_buffer;
70 gsize read_buf_len;
71 gsize read_len;
74 static const char * const socks5errors[] = {
75 "succeeded\n",
76 "general SOCKS server failure\n",
77 "connection not allowed by ruleset\n",
78 "Network unreachable\n",
79 "Host unreachable\n",
80 "Connection refused\n",
81 "TTL expired\n",
82 "Command not supported\n",
83 "Address type not supported\n"
86 static PurpleProxyInfo *global_proxy_info = NULL;
88 static GSList *handles = NULL;
90 static void try_connect(PurpleProxyConnectData *connect_data);
93 * TODO: Eventually (GObjectification) this bad boy will be removed, because it is
94 * a gross fix for a crashy problem.
96 #define PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data) g_slist_find(handles, connect_data)
98 /**************************************************************************
99 * Proxy structure API
100 **************************************************************************/
101 PurpleProxyInfo *
102 purple_proxy_info_new(void)
104 return g_new0(PurpleProxyInfo, 1);
107 void
108 purple_proxy_info_destroy(PurpleProxyInfo *info)
110 g_return_if_fail(info != NULL);
112 g_free(info->host);
113 g_free(info->username);
114 g_free(info->password);
116 g_free(info);
119 void
120 purple_proxy_info_set_type(PurpleProxyInfo *info, PurpleProxyType type)
122 g_return_if_fail(info != NULL);
124 info->type = type;
127 void
128 purple_proxy_info_set_host(PurpleProxyInfo *info, const char *host)
130 g_return_if_fail(info != NULL);
132 g_free(info->host);
133 info->host = g_strdup(host);
136 void
137 purple_proxy_info_set_port(PurpleProxyInfo *info, int port)
139 g_return_if_fail(info != NULL);
141 info->port = port;
144 void
145 purple_proxy_info_set_username(PurpleProxyInfo *info, const char *username)
147 g_return_if_fail(info != NULL);
149 g_free(info->username);
150 info->username = g_strdup(username);
153 void
154 purple_proxy_info_set_password(PurpleProxyInfo *info, const char *password)
156 g_return_if_fail(info != NULL);
158 g_free(info->password);
159 info->password = g_strdup(password);
162 PurpleProxyType
163 purple_proxy_info_get_type(const PurpleProxyInfo *info)
165 g_return_val_if_fail(info != NULL, PURPLE_PROXY_NONE);
167 return info->type;
170 const char *
171 purple_proxy_info_get_host(const PurpleProxyInfo *info)
173 g_return_val_if_fail(info != NULL, NULL);
175 return info->host;
179 purple_proxy_info_get_port(const PurpleProxyInfo *info)
181 g_return_val_if_fail(info != NULL, 0);
183 return info->port;
186 const char *
187 purple_proxy_info_get_username(const PurpleProxyInfo *info)
189 g_return_val_if_fail(info != NULL, NULL);
191 return info->username;
194 const char *
195 purple_proxy_info_get_password(const PurpleProxyInfo *info)
197 g_return_val_if_fail(info != NULL, NULL);
199 return info->password;
202 /**************************************************************************
203 * Global Proxy API
204 **************************************************************************/
205 PurpleProxyInfo *
206 purple_global_proxy_get_info(void)
208 return global_proxy_info;
211 void
212 purple_global_proxy_set_info(PurpleProxyInfo *info)
214 g_return_if_fail(info != NULL);
216 purple_proxy_info_destroy(global_proxy_info);
218 global_proxy_info = info;
221 static PurpleProxyInfo *
222 purple_gnome_proxy_get_info(void)
224 static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
225 gboolean use_same_proxy = FALSE;
226 gchar *tmp, *err = NULL;
228 tmp = g_find_program_in_path("gconftool-2");
229 if (tmp == NULL)
230 return purple_global_proxy_get_info();
232 g_free(tmp);
233 tmp = NULL;
235 /* Check whether to use a proxy. */
236 if (!g_spawn_command_line_sync("gconftool-2 -g /system/proxy/mode",
237 &tmp, &err, NULL, NULL))
238 return purple_global_proxy_get_info();
239 g_free(err);
240 err = NULL;
242 if (purple_strequal(tmp, "none\n")) {
243 info.type = PURPLE_PROXY_NONE;
244 g_free(tmp);
245 return &info;
248 if (!purple_strequal(tmp, "manual\n")) {
249 /* Unknown setting. Fallback to using our global proxy settings. */
250 g_free(tmp);
251 return purple_global_proxy_get_info();
254 g_free(tmp);
255 tmp = NULL;
257 /* Free the old fields */
258 if (info.host) {
259 g_free(info.host);
260 info.host = NULL;
262 if (info.username) {
263 g_free(info.username);
264 info.username = NULL;
266 if (info.password) {
267 g_free(info.password);
268 info.password = NULL;
271 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/use_same_proxy",
272 &tmp, &err, NULL, NULL))
273 return purple_global_proxy_get_info();
274 g_free(err);
275 err = NULL;
277 if (purple_strequal(tmp, "true\n"))
278 use_same_proxy = TRUE;
279 g_free(tmp);
280 tmp = NULL;
282 if (!use_same_proxy) {
283 if (!g_spawn_command_line_sync("gconftool-2 -g /system/proxy/socks_host",
284 &info.host, &err, NULL, NULL))
285 return purple_global_proxy_get_info();
286 g_free(err);
287 err = NULL;
290 if(info.host != NULL)
291 g_strchomp(info.host);
293 if (!use_same_proxy && (info.host != NULL) && (*info.host != '\0')) {
294 info.type = PURPLE_PROXY_SOCKS5;
295 if (!g_spawn_command_line_sync("gconftool-2 -g /system/proxy/socks_port",
296 &tmp, &err, NULL, NULL))
298 g_free(info.host);
299 info.host = NULL;
300 return purple_global_proxy_get_info();
302 g_free(err);
303 info.port = atoi(tmp);
304 g_free(tmp);
305 } else {
306 g_free(info.host);
307 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/host",
308 &info.host, &err, NULL, NULL))
309 return purple_global_proxy_get_info();
310 g_free(err);
311 err = NULL;
313 /* If we get this far then we know we're using an HTTP proxy */
314 info.type = PURPLE_PROXY_HTTP;
316 g_strchomp(info.host);
317 if (*info.host == '\0')
319 purple_debug_info("proxy", "Gnome proxy settings are set to "
320 "'manual' but no suitable proxy server is specified. Using "
321 "Pidgin's proxy settings instead.\n");
322 g_free(info.host);
323 info.host = NULL;
324 return purple_global_proxy_get_info();
327 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/authentication_user",
328 &info.username, &err, NULL, NULL))
330 g_free(info.host);
331 info.host = NULL;
332 return purple_global_proxy_get_info();
334 g_free(err);
335 err = NULL;
336 g_strchomp(info.username);
338 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/authentication_password",
339 &info.password, &err, NULL, NULL))
341 g_free(info.host);
342 info.host = NULL;
343 g_free(info.username);
344 info.username = NULL;
345 return purple_global_proxy_get_info();
347 g_free(err);
348 err = NULL;
349 g_strchomp(info.password);
351 if (!g_spawn_command_line_sync("gconftool-2 -g /system/http_proxy/port",
352 &tmp, &err, NULL, NULL))
354 g_free(info.host);
355 info.host = NULL;
356 g_free(info.username);
357 info.username = NULL;
358 g_free(info.password);
359 info.password = NULL;
360 return purple_global_proxy_get_info();
362 g_free(err);
363 info.port = atoi(tmp);
364 g_free(tmp);
367 return &info;
370 #ifdef _WIN32
372 typedef BOOL (CALLBACK* LPFNWINHTTPGETIEPROXYCONFIG)(/*IN OUT*/ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* pProxyConfig);
374 /* This modifies "host" in-place evilly */
375 static void
376 _proxy_fill_hostinfo(PurpleProxyInfo *info, char *host, int default_port)
378 int port = default_port;
379 char *d;
381 d = g_strrstr(host, ":");
382 if (d)
383 *d = '\0';
384 d++;
385 if (*d)
386 sscanf(d, "%d", &port);
388 purple_proxy_info_set_host(info, host);
389 purple_proxy_info_set_port(info, port);
392 static PurpleProxyInfo *
393 purple_win32_proxy_get_info(void)
395 static LPFNWINHTTPGETIEPROXYCONFIG MyWinHttpGetIEProxyConfig = NULL;
396 static gboolean loaded = FALSE;
397 static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
399 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_proxy_config;
401 if (!loaded) {
402 loaded = TRUE;
403 MyWinHttpGetIEProxyConfig = (LPFNWINHTTPGETIEPROXYCONFIG)
404 wpurple_find_and_loadproc("winhttp.dll", "WinHttpGetIEProxyConfigForCurrentUser");
405 if (!MyWinHttpGetIEProxyConfig)
406 purple_debug_warning("proxy", "Unable to read Windows Proxy Settings.\n");
409 if (!MyWinHttpGetIEProxyConfig)
410 return NULL;
412 ZeroMemory(&ie_proxy_config, sizeof(ie_proxy_config));
413 if (!MyWinHttpGetIEProxyConfig(&ie_proxy_config)) {
414 purple_debug_error("proxy", "Error reading Windows Proxy Settings(%lu).\n", GetLastError());
415 return NULL;
418 /* We can't do much if it is autodetect*/
419 if (ie_proxy_config.fAutoDetect) {
420 purple_debug_error("proxy", "Windows Proxy Settings set to autodetect (not supported).\n");
422 /* TODO: For 3.0.0 we'll revisit this (maybe)*/
424 return NULL;
426 } else if (ie_proxy_config.lpszProxy) {
427 gchar *proxy_list = g_utf16_to_utf8(ie_proxy_config.lpszProxy, -1,
428 NULL, NULL, NULL);
430 /* We can't do anything about the bypass list, as we don't have the url */
431 /* TODO: For 3.0.0 we'll revisit this*/
433 /* There are proxy settings for several protocols */
434 if (proxy_list && *proxy_list) {
435 char *specific = NULL, *tmp;
437 /* If there is only a global proxy, which means "HTTP" */
438 if (!strchr(proxy_list, ';') || (specific = g_strstr_len(proxy_list, -1, "http=")) != NULL) {
440 if (specific) {
441 specific += strlen("http=");
442 tmp = strchr(specific, ';');
443 if (tmp)
444 *tmp = '\0';
445 /* specific now points the proxy server (and port) */
446 } else
447 specific = proxy_list;
449 purple_proxy_info_set_type(&info, PURPLE_PROXY_HTTP);
450 _proxy_fill_hostinfo(&info, specific, 80);
451 /* TODO: is there a way to set the username/password? */
452 purple_proxy_info_set_username(&info, NULL);
453 purple_proxy_info_set_password(&info, NULL);
455 purple_debug_info("proxy", "Windows Proxy Settings: HTTP proxy: '%s:%d'.\n",
456 purple_proxy_info_get_host(&info),
457 purple_proxy_info_get_port(&info));
459 } else if ((specific = g_strstr_len(proxy_list, -1, "socks=")) != NULL) {
461 specific += strlen("socks=");
462 tmp = strchr(specific, ';');
463 if (tmp)
464 *tmp = '\0';
465 /* specific now points the proxy server (and port) */
467 purple_proxy_info_set_type(&info, PURPLE_PROXY_SOCKS5);
468 _proxy_fill_hostinfo(&info, specific, 1080);
469 /* TODO: is there a way to set the username/password? */
470 purple_proxy_info_set_username(&info, NULL);
471 purple_proxy_info_set_password(&info, NULL);
473 purple_debug_info("proxy", "Windows Proxy Settings: SOCKS5 proxy: '%s:%d'.\n",
474 purple_proxy_info_get_host(&info),
475 purple_proxy_info_get_port(&info));
477 } else {
479 purple_debug_info("proxy", "Windows Proxy Settings: No supported proxy specified.\n");
481 purple_proxy_info_set_type(&info, PURPLE_PROXY_NONE);
486 /* TODO: Fix API to be able look at proxy bypass settings */
488 g_free(proxy_list);
489 } else {
490 purple_debug_info("proxy", "No Windows proxy set.\n");
491 purple_proxy_info_set_type(&info, PURPLE_PROXY_NONE);
494 if (ie_proxy_config.lpszAutoConfigUrl)
495 GlobalFree(ie_proxy_config.lpszAutoConfigUrl);
496 if (ie_proxy_config.lpszProxy)
497 GlobalFree(ie_proxy_config.lpszProxy);
498 if (ie_proxy_config.lpszProxyBypass)
499 GlobalFree(ie_proxy_config.lpszProxyBypass);
501 return &info;
503 #endif
506 /**************************************************************************
507 * Proxy API
508 **************************************************************************/
511 * Whoever calls this needs to have called
512 * purple_proxy_connect_data_disconnect() beforehand.
514 static void
515 purple_proxy_connect_data_destroy(PurpleProxyConnectData *connect_data)
517 handles = g_slist_remove(handles, connect_data);
519 if (connect_data->query_data != NULL)
520 purple_dnsquery_destroy(connect_data->query_data);
522 while (connect_data->hosts != NULL)
524 /* Discard the length... */
525 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
526 /* Free the address... */
527 g_free(connect_data->hosts->data);
528 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
531 g_free(connect_data->host);
532 g_free(connect_data);
536 * Free all information dealing with a connection attempt and
537 * reset the connect_data to prepare for it to try to connect
538 * to another IP address.
540 * If an error message is passed in, then we know the connection
541 * attempt failed. If the connection attempt failed and
542 * connect_data->hosts is not empty then we try the next IP address.
543 * If the connection attempt failed and we have no more hosts
544 * try try then we call the callback with the given error message,
545 * then destroy the connect_data.
547 * @param error_message An error message explaining why the connection
548 * failed. This will be passed to the callback function
549 * specified in the call to purple_proxy_connect(). If the
550 * connection was successful then pass in null.
552 static void
553 purple_proxy_connect_data_disconnect(PurpleProxyConnectData *connect_data, const gchar *error_message)
555 if (connect_data->inpa > 0)
557 purple_input_remove(connect_data->inpa);
558 connect_data->inpa = 0;
561 if (connect_data->fd >= 0)
563 close(connect_data->fd);
564 connect_data->fd = -1;
567 g_free(connect_data->write_buffer);
568 connect_data->write_buffer = NULL;
570 g_free(connect_data->read_buffer);
571 connect_data->read_buffer = NULL;
573 if (error_message != NULL)
575 purple_debug_error("proxy", "Connection attempt failed: %s\n",
576 error_message);
577 if (connect_data->hosts != NULL)
578 try_connect(connect_data);
579 else
581 /* Everything failed! Tell the originator of the request. */
582 connect_data->connect_cb(connect_data->data, -1, error_message);
583 purple_proxy_connect_data_destroy(connect_data);
589 * This calls purple_proxy_connect_data_disconnect(), but it lets you
590 * specify the error_message using a printf()-like syntax.
592 static void
593 purple_proxy_connect_data_disconnect_formatted(PurpleProxyConnectData *connect_data, const char *format, ...)
595 va_list args;
596 gchar *tmp;
598 va_start(args, format);
599 tmp = g_strdup_vprintf(format, args);
600 va_end(args);
602 purple_proxy_connect_data_disconnect(connect_data, tmp);
603 g_free(tmp);
606 static void
607 purple_proxy_connect_data_connected(PurpleProxyConnectData *connect_data)
609 purple_debug_info("proxy", "Connected to %s:%d.\n",
610 connect_data->host, connect_data->port);
612 connect_data->connect_cb(connect_data->data, connect_data->fd, NULL);
615 * We've passed the file descriptor to the protocol, so it's no longer
616 * our responsibility, and we should be careful not to free it when
617 * we destroy the connect_data.
619 connect_data->fd = -1;
621 purple_proxy_connect_data_disconnect(connect_data, NULL);
622 purple_proxy_connect_data_destroy(connect_data);
625 static void
626 socket_ready_cb(gpointer data, gint source, PurpleInputCondition cond)
628 PurpleProxyConnectData *connect_data = data;
629 int error = 0;
630 int ret;
632 /* If the socket-connected message had already been triggered when connect_data
633 * was destroyed via purple_proxy_connect_cancel(), we may get here with a freed connect_data.
635 if (!PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data))
636 return;
638 purple_debug_info("proxy", "Connecting to %s:%d.\n",
639 connect_data->host, connect_data->port);
642 * purple_input_get_error after a non-blocking connect returns -1 if something is
643 * really messed up (bad descriptor, usually). Otherwise, it returns 0 and
644 * error holds what connect would have returned if it blocked until now.
645 * Thus, error == 0 is success, error == EINPROGRESS means "try again",
646 * and anything else is a real error.
648 * (error == EINPROGRESS can happen after a select because the kernel can
649 * be overly optimistic sometimes. select is just a hint that you might be
650 * able to do something.)
652 ret = purple_input_get_error(connect_data->fd, &error);
654 if (ret == 0 && error == EINPROGRESS) {
655 /* No worries - we'll be called again later */
656 /* TODO: Does this ever happen? */
657 purple_debug_info("proxy", "(ret == 0 && error == EINPROGRESS)\n");
658 return;
661 if (ret != 0 || error != 0) {
662 if (ret != 0)
663 error = errno;
664 purple_debug_error("proxy", "Error connecting to %s:%d (%s).\n",
665 connect_data->host, connect_data->port, g_strerror(error));
667 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
668 return;
671 purple_proxy_connect_data_connected(connect_data);
674 static gboolean
675 clean_connect(gpointer data)
677 purple_proxy_connect_data_connected(data);
679 return FALSE;
682 static void
683 proxy_connect_udp_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
685 int flags;
687 purple_debug_info("proxy", "UDP Connecting to %s:%d with no proxy\n",
688 connect_data->host, connect_data->port);
690 connect_data->fd = socket(addr->sa_family, SOCK_DGRAM, 0);
691 if (connect_data->fd < 0)
693 purple_proxy_connect_data_disconnect_formatted(connect_data,
694 _("Unable to create socket: %s"), g_strerror(errno));
695 return;
698 flags = fcntl(connect_data->fd, F_GETFL);
699 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
700 #ifndef _WIN32
701 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
702 #endif
704 if (connect(connect_data->fd, addr, addrlen) != 0)
706 if ((errno == EINPROGRESS) || (errno == EINTR))
708 purple_debug_info("proxy", "UDP Connection in progress\n");
709 connect_data->inpa = purple_input_add(connect_data->fd,
710 PURPLE_INPUT_WRITE, socket_ready_cb, connect_data);
712 else
714 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
717 else
720 * The connection happened IMMEDIATELY... strange, but whatever.
722 int error = ETIMEDOUT;
723 int ret;
725 purple_debug_info("proxy", "UDP Connected immediately.\n");
727 ret = purple_input_get_error(connect_data->fd, &error);
728 if ((ret != 0) || (error != 0))
730 if (ret != 0)
731 error = errno;
732 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
733 return;
737 * We want to call the "connected" callback eventually, but we
738 * don't want to call it before we return, just in case.
740 purple_timeout_add(10, clean_connect, connect_data);
744 static void
745 proxy_connect_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
747 int flags;
749 purple_debug_info("proxy", "Connecting to %s:%d with no proxy\n",
750 connect_data->host, connect_data->port);
752 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
753 if (connect_data->fd < 0)
755 purple_proxy_connect_data_disconnect_formatted(connect_data,
756 _("Unable to create socket: %s"), g_strerror(errno));
757 return;
760 flags = fcntl(connect_data->fd, F_GETFL);
761 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
762 #ifndef _WIN32
763 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
764 #endif
766 if (connect(connect_data->fd, addr, addrlen) != 0)
768 if ((errno == EINPROGRESS) || (errno == EINTR))
770 purple_debug_info("proxy", "Connection in progress\n");
771 connect_data->inpa = purple_input_add(connect_data->fd,
772 PURPLE_INPUT_WRITE, socket_ready_cb, connect_data);
774 else
776 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
779 else
782 * The connection happened IMMEDIATELY... strange, but whatever.
784 int error = ETIMEDOUT;
785 int ret;
787 purple_debug_info("proxy", "Connected immediately.\n");
789 ret = purple_input_get_error(connect_data->fd, &error);
790 if ((ret != 0) || (error != 0))
792 if (ret != 0)
793 error = errno;
794 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
795 return;
799 * We want to call the "connected" callback eventually, but we
800 * don't want to call it before we return, just in case.
802 purple_timeout_add(10, clean_connect, connect_data);
807 * This is a utility function used by the HTTP, SOCKS4 and SOCKS5
808 * connect functions. It writes data from a buffer to a socket.
809 * When all the data is written it sets up a watcher to read a
810 * response and call a specified function.
812 static void
813 proxy_do_write(gpointer data, gint source, PurpleInputCondition cond)
815 PurpleProxyConnectData *connect_data;
816 const guchar *request;
817 gsize request_len;
818 int ret;
820 connect_data = data;
821 request = connect_data->write_buffer + connect_data->written_len;
822 request_len = connect_data->write_buf_len - connect_data->written_len;
824 ret = write(connect_data->fd, request, request_len);
825 if (ret <= 0)
827 if (errno == EAGAIN)
828 /* No worries */
829 return;
831 /* Error! */
832 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
833 return;
835 if (ret < request_len) {
836 connect_data->written_len += ret;
837 return;
840 /* We're done writing data! Wait for a response. */
841 g_free(connect_data->write_buffer);
842 connect_data->write_buffer = NULL;
843 purple_input_remove(connect_data->inpa);
844 connect_data->inpa = purple_input_add(connect_data->fd,
845 PURPLE_INPUT_READ, connect_data->read_cb, connect_data);
848 #define HTTP_GOODSTRING "HTTP/1.0 200"
849 #define HTTP_GOODSTRING2 "HTTP/1.1 200"
852 * We're using an HTTP proxy for a non-port 80 tunnel. Read the
853 * response to the CONNECT request.
855 static void
856 http_canread(gpointer data, gint source, PurpleInputCondition cond)
858 int len, headers_len, status = 0;
859 gboolean error;
860 PurpleProxyConnectData *connect_data = data;
861 char *p;
862 gsize max_read;
864 if (connect_data->read_buffer == NULL) {
865 connect_data->read_buf_len = 8192;
866 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
867 connect_data->read_len = 0;
870 p = (char *)connect_data->read_buffer + connect_data->read_len;
871 max_read = connect_data->read_buf_len - connect_data->read_len - 1;
873 len = read(connect_data->fd, p, max_read);
875 if (len == 0) {
876 purple_proxy_connect_data_disconnect(connect_data,
877 _("Server closed the connection"));
878 return;
881 if (len < 0) {
882 if (errno == EAGAIN)
883 /* No worries */
884 return;
886 /* Error! */
887 purple_proxy_connect_data_disconnect_formatted(connect_data,
888 _("Lost connection with server: %s"), g_strerror(errno));
889 return;
892 connect_data->read_len += len;
893 p[len] = '\0';
895 p = g_strstr_len((const gchar *)connect_data->read_buffer,
896 connect_data->read_len, "\r\n\r\n");
897 if (p != NULL) {
898 *p = '\0';
899 headers_len = (p - (char *)connect_data->read_buffer) + 4;
900 } else if(len == max_read)
901 headers_len = len;
902 else
903 return;
905 error = strncmp((const char *)connect_data->read_buffer, "HTTP/", 5) != 0;
906 if (!error) {
907 int major;
908 p = (char *)connect_data->read_buffer + 5;
909 major = strtol(p, &p, 10);
910 error = (major == 0) || (*p != '.');
911 if(!error) {
912 int minor;
913 p++;
914 minor = strtol(p, &p, 10);
915 error = (*p != ' ');
916 if(!error) {
917 p++;
918 status = strtol(p, &p, 10);
919 error = (*p != ' ');
924 /* Read the contents */
925 p = g_strrstr((const gchar *)connect_data->read_buffer, "Content-Length: ");
926 if (p != NULL) {
927 gchar *tmp;
928 int len = 0;
929 char tmpc;
930 p += strlen("Content-Length: ");
931 tmp = strchr(p, '\r');
932 if(tmp)
933 *tmp = '\0';
934 len = atoi(p);
935 if(tmp)
936 *tmp = '\r';
938 /* Compensate for what has already been read */
939 len -= connect_data->read_len - headers_len;
940 /* I'm assuming that we're doing this to prevent the server from
941 complaining / breaking since we don't read the whole page */
942 while (len--) {
943 /* TODO: deal with EAGAIN (and other errors) better */
944 if (read(connect_data->fd, &tmpc, 1) < 0 && errno != EAGAIN)
945 break;
949 if (error) {
950 purple_proxy_connect_data_disconnect_formatted(connect_data,
951 _("Unable to parse response from HTTP proxy: %s"),
952 connect_data->read_buffer);
953 return;
955 else if (status != 200) {
956 purple_debug_error("proxy",
957 "Proxy server replied with:\n%s\n",
958 connect_data->read_buffer);
960 if (status == 407 /* Proxy Auth */) {
961 const char *header;
962 gchar *request;
964 header = g_strrstr((const gchar *)connect_data->read_buffer,
965 "Proxy-Authenticate: NTLM");
966 if (header != NULL) {
967 const char *header_end = header + strlen("Proxy-Authenticate: NTLM");
968 const char *domain = purple_proxy_info_get_username(connect_data->gpi);
969 char *username = NULL, hostname[256];
970 gchar *response;
971 int ret;
973 ret = gethostname(hostname, sizeof(hostname));
974 hostname[sizeof(hostname) - 1] = '\0';
975 if (ret < 0 || hostname[0] == '\0') {
976 purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?");
977 strcpy(hostname, "localhost");
980 if (domain != NULL)
981 username = (char*) strchr(domain, '\\');
982 if (username == NULL) {
983 purple_proxy_connect_data_disconnect_formatted(connect_data,
984 _("HTTP proxy connection error %d"), status);
985 return;
987 *username = '\0';
989 /* Is there a message? */
990 if (*header_end == ' ') {
991 /* Check for Type-2 */
992 char *tmp = (char*) header;
993 guint8 *nonce;
995 header_end++;
996 username++;
997 while(*tmp != '\r' && *tmp != '\0') tmp++;
998 *tmp = '\0';
999 nonce = purple_ntlm_parse_type2(header_end, NULL);
1000 response = purple_ntlm_gen_type3(username,
1001 (gchar*) purple_proxy_info_get_password(connect_data->gpi),
1002 hostname,
1003 domain, nonce, NULL);
1004 username--;
1005 } else /* Empty message */
1006 response = purple_ntlm_gen_type1(hostname, domain);
1008 *username = '\\';
1010 request = g_strdup_printf(
1011 "CONNECT %s:%d HTTP/1.1\r\n"
1012 "Host: %s:%d\r\n"
1013 "Proxy-Authorization: NTLM %s\r\n"
1014 "Proxy-Connection: Keep-Alive\r\n\r\n",
1015 connect_data->host, connect_data->port,
1016 connect_data->host, connect_data->port,
1017 response);
1019 g_free(response);
1021 } else if((header = g_strrstr((const char *)connect_data->read_buffer, "Proxy-Authenticate: Basic"))) {
1022 gchar *t1, *t2;
1023 const char *username, *password;
1025 username = purple_proxy_info_get_username(connect_data->gpi);
1026 password = purple_proxy_info_get_password(connect_data->gpi);
1028 t1 = g_strdup_printf("%s:%s",
1029 username ? username : "",
1030 password ? password : "");
1031 t2 = purple_base64_encode((guchar *)t1, strlen(t1));
1032 g_free(t1);
1034 request = g_strdup_printf(
1035 "CONNECT %s:%d HTTP/1.1\r\n"
1036 "Host: %s:%d\r\n"
1037 "Proxy-Authorization: Basic %s\r\n",
1038 connect_data->host, connect_data->port,
1039 connect_data->host, connect_data->port,
1040 t2);
1042 g_free(t2);
1044 } else {
1045 purple_proxy_connect_data_disconnect_formatted(connect_data,
1046 _("HTTP proxy connection error %d"), status);
1047 return;
1050 purple_input_remove(connect_data->inpa);
1051 g_free(connect_data->read_buffer);
1052 connect_data->read_buffer = NULL;
1054 connect_data->write_buffer = (guchar *)request;
1055 connect_data->write_buf_len = strlen(request);
1056 connect_data->written_len = 0;
1058 connect_data->read_cb = http_canread;
1060 connect_data->inpa = purple_input_add(connect_data->fd,
1061 PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1063 proxy_do_write(connect_data, connect_data->fd, cond);
1065 return;
1068 if (status == 403) {
1069 /* Forbidden */
1070 purple_proxy_connect_data_disconnect_formatted(connect_data,
1071 _("Access denied: HTTP proxy server forbids port %d tunneling"),
1072 connect_data->port);
1073 } else {
1074 purple_proxy_connect_data_disconnect_formatted(connect_data,
1075 _("HTTP proxy connection error %d"), status);
1077 } else {
1078 purple_input_remove(connect_data->inpa);
1079 connect_data->inpa = 0;
1080 g_free(connect_data->read_buffer);
1081 connect_data->read_buffer = NULL;
1082 purple_debug_info("proxy", "HTTP proxy connection established\n");
1083 purple_proxy_connect_data_connected(connect_data);
1084 return;
1088 static void
1089 http_start_connect_tunneling(PurpleProxyConnectData *connect_data) {
1090 GString *request;
1091 int ret;
1093 purple_debug_info("proxy", "Using CONNECT tunneling for %s:%d\n",
1094 connect_data->host, connect_data->port);
1096 request = g_string_sized_new(4096);
1097 g_string_append_printf(request,
1098 "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n",
1099 connect_data->host, connect_data->port,
1100 connect_data->host, connect_data->port);
1102 if (purple_proxy_info_get_username(connect_data->gpi) != NULL)
1104 char *t1, *t2, *ntlm_type1;
1105 char hostname[256];
1107 ret = gethostname(hostname, sizeof(hostname));
1108 hostname[sizeof(hostname) - 1] = '\0';
1109 if (ret < 0 || hostname[0] == '\0') {
1110 purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?");
1111 strcpy(hostname, "localhost");
1114 t1 = g_strdup_printf("%s:%s",
1115 purple_proxy_info_get_username(connect_data->gpi),
1116 purple_proxy_info_get_password(connect_data->gpi) ?
1117 purple_proxy_info_get_password(connect_data->gpi) : "");
1118 t2 = purple_base64_encode((const guchar *)t1, strlen(t1));
1119 g_free(t1);
1121 ntlm_type1 = purple_ntlm_gen_type1(hostname, "");
1123 g_string_append_printf(request,
1124 "Proxy-Authorization: Basic %s\r\n"
1125 "Proxy-Authorization: NTLM %s\r\n"
1126 "Proxy-Connection: Keep-Alive\r\n",
1127 t2, ntlm_type1);
1128 g_free(ntlm_type1);
1129 g_free(t2);
1132 g_string_append(request, "\r\n");
1134 connect_data->write_buf_len = request->len;
1135 connect_data->write_buffer = (guchar *)g_string_free(request, FALSE);
1136 connect_data->written_len = 0;
1137 connect_data->read_cb = http_canread;
1139 connect_data->inpa = purple_input_add(connect_data->fd,
1140 PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1141 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1144 static void
1145 http_canwrite(gpointer data, gint source, PurpleInputCondition cond) {
1146 PurpleProxyConnectData *connect_data = data;
1147 int ret, error = ETIMEDOUT;
1149 purple_debug_info("proxy", "Connected to %s:%d.\n",
1150 connect_data->host, connect_data->port);
1152 if (connect_data->inpa > 0) {
1153 purple_input_remove(connect_data->inpa);
1154 connect_data->inpa = 0;
1157 ret = purple_input_get_error(connect_data->fd, &error);
1158 if (ret != 0 || error != 0) {
1159 if (ret != 0)
1160 error = errno;
1161 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
1162 return;
1165 if (connect_data->port == 80) {
1167 * If we're trying to connect to something running on
1168 * port 80 then we assume the traffic using this
1169 * connection is going to be HTTP traffic. If it's
1170 * not then this will fail (uglily). But it's good
1171 * to avoid using the CONNECT method because it's
1172 * not always allowed.
1174 purple_debug_info("proxy", "HTTP proxy connection established\n");
1175 purple_proxy_connect_data_connected(connect_data);
1176 } else {
1177 http_start_connect_tunneling(connect_data);
1182 static void
1183 proxy_connect_http(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
1185 int flags;
1187 purple_debug_info("proxy",
1188 "Connecting to %s:%d via %s:%d using HTTP\n",
1189 connect_data->host, connect_data->port,
1190 (purple_proxy_info_get_host(connect_data->gpi) ? purple_proxy_info_get_host(connect_data->gpi) : "(null)"),
1191 purple_proxy_info_get_port(connect_data->gpi));
1193 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
1194 if (connect_data->fd < 0)
1196 purple_proxy_connect_data_disconnect_formatted(connect_data,
1197 _("Unable to create socket: %s"), g_strerror(errno));
1198 return;
1201 flags = fcntl(connect_data->fd, F_GETFL);
1202 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
1203 #ifndef _WIN32
1204 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
1205 #endif
1207 if (connect(connect_data->fd, addr, addrlen) != 0) {
1208 if (errno == EINPROGRESS || errno == EINTR) {
1209 purple_debug_info("proxy", "Connection in progress\n");
1211 connect_data->inpa = purple_input_add(connect_data->fd,
1212 PURPLE_INPUT_WRITE, http_canwrite, connect_data);
1213 } else
1214 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
1215 } else {
1216 purple_debug_info("proxy", "Connected immediately.\n");
1218 http_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1222 static void
1223 s4_canread(gpointer data, gint source, PurpleInputCondition cond)
1225 PurpleProxyConnectData *connect_data = data;
1226 guchar *buf;
1227 int len, max_read;
1229 /* This is really not going to block under normal circumstances, but to
1230 * be correct, we deal with the unlikely scenario */
1232 if (connect_data->read_buffer == NULL) {
1233 connect_data->read_buf_len = 12;
1234 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1235 connect_data->read_len = 0;
1238 buf = connect_data->read_buffer + connect_data->read_len;
1239 max_read = connect_data->read_buf_len - connect_data->read_len;
1241 len = read(connect_data->fd, buf, max_read);
1243 if ((len < 0 && errno == EAGAIN) || (len > 0 && len + connect_data->read_len < 4))
1244 return;
1245 else if (len + connect_data->read_len >= 4) {
1246 if (connect_data->read_buffer[1] == 90) {
1247 purple_proxy_connect_data_connected(connect_data);
1248 return;
1252 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
1255 static void
1256 s4_host_resolved(GSList *hosts, gpointer data, const char *error_message)
1258 PurpleProxyConnectData *connect_data = data;
1259 unsigned char packet[9];
1260 struct sockaddr *addr;
1262 connect_data->query_data = NULL;
1264 if (error_message != NULL) {
1265 purple_proxy_connect_data_disconnect(connect_data, error_message);
1266 return;
1269 if (hosts == NULL) {
1270 purple_proxy_connect_data_disconnect_formatted(connect_data,
1271 _("Error resolving %s"), connect_data->host);
1272 return;
1275 /* Discard the length... */
1276 hosts = g_slist_delete_link(hosts, hosts);
1277 addr = hosts->data;
1278 hosts = g_slist_delete_link(hosts, hosts);
1280 packet[0] = 0x04;
1281 packet[1] = 0x01;
1282 packet[2] = connect_data->port >> 8;
1283 packet[3] = connect_data->port & 0xff;
1284 memcpy(packet + 4, &((struct sockaddr_in *)addr)->sin_addr.s_addr, 4);
1285 packet[8] = 0x00;
1287 g_free(addr);
1289 /* We could try the other hosts, but hopefully that shouldn't be necessary */
1290 while (hosts != NULL) {
1291 /* Discard the length... */
1292 hosts = g_slist_delete_link(hosts, hosts);
1293 /* Free the address... */
1294 g_free(hosts->data);
1295 hosts = g_slist_delete_link(hosts, hosts);
1298 connect_data->write_buffer = g_memdup(packet, sizeof(packet));
1299 connect_data->write_buf_len = sizeof(packet);
1300 connect_data->written_len = 0;
1301 connect_data->read_cb = s4_canread;
1303 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1305 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1308 static void
1309 s4_canwrite(gpointer data, gint source, PurpleInputCondition cond)
1311 PurpleProxyConnectData *connect_data = data;
1312 int error = ETIMEDOUT;
1313 int ret;
1315 purple_debug_info("socks4 proxy", "Connected.\n");
1317 if (connect_data->inpa > 0) {
1318 purple_input_remove(connect_data->inpa);
1319 connect_data->inpa = 0;
1322 ret = purple_input_get_error(connect_data->fd, &error);
1323 if ((ret != 0) || (error != 0)) {
1324 if (ret != 0)
1325 error = errno;
1326 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
1327 return;
1331 * The socks4 spec doesn't include support for doing host name lookups by
1332 * the proxy. Many socks4 servers do this via the "socks4a" extension to
1333 * the protocol. There doesn't appear to be a way to detect if a server
1334 * supports this, so we require that the user set a global option.
1336 if (purple_prefs_get_bool("/purple/proxy/socks4_remotedns")) {
1337 unsigned char packet[9];
1338 int len;
1340 purple_debug_info("socks4 proxy", "Attempting to use remote DNS.\n");
1342 packet[0] = 0x04;
1343 packet[1] = 0x01;
1344 packet[2] = connect_data->port >> 8;
1345 packet[3] = connect_data->port & 0xff;
1346 packet[4] = 0x00;
1347 packet[5] = 0x00;
1348 packet[6] = 0x00;
1349 packet[7] = 0x01;
1350 packet[8] = 0x00;
1352 len = sizeof(packet) + strlen(connect_data->host) + 1;
1354 connect_data->write_buffer = g_malloc0(len);
1355 memcpy(connect_data->write_buffer, packet, sizeof(packet));
1356 memcpy(connect_data->write_buffer + sizeof(packet), connect_data->host, strlen(connect_data->host));
1357 connect_data->write_buf_len = len;
1358 connect_data->written_len = 0;
1359 connect_data->read_cb = s4_canread;
1361 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1363 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1364 } else {
1365 connect_data->query_data = purple_dnsquery_a(connect_data->host,
1366 connect_data->port, s4_host_resolved, connect_data);
1368 if (connect_data->query_data == NULL) {
1369 purple_debug_error("proxy", "dns query failed unexpectedly.\n");
1370 purple_proxy_connect_data_destroy(connect_data);
1375 static void
1376 proxy_connect_socks4(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
1378 int flags;
1380 purple_debug_info("proxy",
1381 "Connecting to %s:%d via %s:%d using SOCKS4\n",
1382 connect_data->host, connect_data->port,
1383 purple_proxy_info_get_host(connect_data->gpi),
1384 purple_proxy_info_get_port(connect_data->gpi));
1386 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
1387 if (connect_data->fd < 0)
1389 purple_proxy_connect_data_disconnect_formatted(connect_data,
1390 _("Unable to create socket: %s"), g_strerror(errno));
1391 return;
1394 flags = fcntl(connect_data->fd, F_GETFL);
1395 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
1396 #ifndef _WIN32
1397 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
1398 #endif
1400 if (connect(connect_data->fd, addr, addrlen) != 0)
1402 if ((errno == EINPROGRESS) || (errno == EINTR))
1404 purple_debug_info("proxy", "Connection in progress.\n");
1405 connect_data->inpa = purple_input_add(connect_data->fd,
1406 PURPLE_INPUT_WRITE, s4_canwrite, connect_data);
1408 else
1410 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
1413 else
1415 purple_debug_info("proxy", "Connected immediately.\n");
1417 s4_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1421 static gboolean
1422 s5_ensure_buffer_length(PurpleProxyConnectData *connect_data, int len)
1424 if(connect_data->read_len < len) {
1425 if(connect_data->read_buf_len < len) {
1426 /* it's not just that we haven't read enough, it's that we haven't tried to read enough yet */
1427 purple_debug_info("s5", "reallocing from %" G_GSIZE_FORMAT
1428 " to %d\n", connect_data->read_buf_len, len);
1429 connect_data->read_buf_len = len;
1430 connect_data->read_buffer = g_realloc(connect_data->read_buffer, connect_data->read_buf_len);
1432 return FALSE;
1435 return TRUE;
1438 static void
1439 s5_canread_again(gpointer data, gint source, PurpleInputCondition cond)
1441 guchar *dest, *buf;
1442 PurpleProxyConnectData *connect_data = data;
1443 int len;
1445 if (connect_data->read_buffer == NULL) {
1446 connect_data->read_buf_len = 5;
1447 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1448 connect_data->read_len = 0;
1451 dest = connect_data->read_buffer + connect_data->read_len;
1452 buf = connect_data->read_buffer;
1454 len = read(connect_data->fd, dest, (connect_data->read_buf_len - connect_data->read_len));
1456 if (len == 0)
1458 purple_proxy_connect_data_disconnect(connect_data,
1459 _("Server closed the connection"));
1460 return;
1463 if (len < 0)
1465 if (errno == EAGAIN)
1466 /* No worries */
1467 return;
1469 /* Error! */
1470 purple_proxy_connect_data_disconnect_formatted(connect_data,
1471 _("Lost connection with server: %s"), g_strerror(errno));
1472 return;
1475 connect_data->read_len += len;
1477 if(connect_data->read_len < 4)
1478 return;
1480 if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
1481 if ((buf[0] == 0x05) && (buf[1] < 0x09)) {
1482 purple_debug_error("socks5 proxy", "%s", socks5errors[buf[1]]);
1483 purple_proxy_connect_data_disconnect(connect_data,
1484 socks5errors[buf[1]]);
1485 } else {
1486 purple_debug_error("socks5 proxy", "Bad data.\n");
1487 purple_proxy_connect_data_disconnect(connect_data,
1488 _("Received invalid data on connection with server"));
1490 return;
1493 /* Skip past BND.ADDR */
1494 switch(buf[3]) {
1495 case 0x01: /* the address is a version-4 IP address, with a length of 4 octets */
1496 if(!s5_ensure_buffer_length(connect_data, 4 + 4))
1497 return;
1498 buf += 4 + 4;
1499 break;
1500 case 0x03: /* the address field contains a fully-qualified domain name. The first
1501 octet of the address field contains the number of octets of name that
1502 follow, there is no terminating NUL octet. */
1503 if(!s5_ensure_buffer_length(connect_data, 4 + 1))
1504 return;
1505 buf += 4;
1506 if(!s5_ensure_buffer_length(connect_data, 4 + 1 + buf[0]))
1507 return;
1508 buf += buf[0] + 1;
1509 break;
1510 case 0x04: /* the address is a version-6 IP address, with a length of 16 octets */
1511 if(!s5_ensure_buffer_length(connect_data, 4 + 16))
1512 return;
1513 buf += 4 + 16;
1514 break;
1515 default:
1516 purple_debug_error("socks5 proxy", "Invalid ATYP received (0x%X)\n", buf[3]);
1517 purple_proxy_connect_data_disconnect(connect_data,
1518 _("Received invalid data on connection with server"));
1519 return;
1522 /* Skip past BND.PORT */
1523 if(!s5_ensure_buffer_length(connect_data, (buf - connect_data->read_buffer) + 2))
1524 return;
1526 purple_proxy_connect_data_connected(connect_data);
1529 static void
1530 s5_sendconnect(gpointer data, int source)
1532 PurpleProxyConnectData *connect_data = data;
1533 size_t hlen = strlen(connect_data->host);
1534 connect_data->write_buf_len = 5 + hlen + 2;
1535 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1536 connect_data->written_len = 0;
1538 connect_data->write_buffer[0] = 0x05;
1539 connect_data->write_buffer[1] = 0x01; /* CONNECT */
1540 connect_data->write_buffer[2] = 0x00; /* reserved */
1541 connect_data->write_buffer[3] = 0x03; /* address type -- host name */
1542 connect_data->write_buffer[4] = hlen;
1543 memcpy(connect_data->write_buffer + 5, connect_data->host, hlen);
1544 connect_data->write_buffer[5 + hlen] = connect_data->port >> 8;
1545 connect_data->write_buffer[5 + hlen + 1] = connect_data->port & 0xff;
1547 connect_data->read_cb = s5_canread_again;
1549 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1550 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1553 static void
1554 s5_readauth(gpointer data, gint source, PurpleInputCondition cond)
1556 PurpleProxyConnectData *connect_data = data;
1557 int len;
1559 if (connect_data->read_buffer == NULL) {
1560 connect_data->read_buf_len = 2;
1561 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1562 connect_data->read_len = 0;
1565 purple_debug_info("socks5 proxy", "Got auth response.\n");
1567 len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
1568 connect_data->read_buf_len - connect_data->read_len);
1570 if (len == 0)
1572 purple_proxy_connect_data_disconnect(connect_data,
1573 _("Server closed the connection"));
1574 return;
1577 if (len < 0)
1579 if (errno == EAGAIN)
1580 /* No worries */
1581 return;
1583 /* Error! */
1584 purple_proxy_connect_data_disconnect_formatted(connect_data,
1585 _("Lost connection with server: %s"), g_strerror(errno));
1586 return;
1589 connect_data->read_len += len;
1590 if (connect_data->read_len < 2)
1591 return;
1593 purple_input_remove(connect_data->inpa);
1594 connect_data->inpa = 0;
1596 if ((connect_data->read_buffer[0] != 0x01) || (connect_data->read_buffer[1] != 0x00)) {
1597 purple_proxy_connect_data_disconnect(connect_data,
1598 _("Received invalid data on connection with server"));
1599 return;
1602 g_free(connect_data->read_buffer);
1603 connect_data->read_buffer = NULL;
1605 s5_sendconnect(connect_data, connect_data->fd);
1608 static void
1609 hmacmd5_chap(const unsigned char * challenge, int challen, const char * passwd, unsigned char * response)
1611 PurpleCipher *cipher;
1612 PurpleCipherContext *ctx;
1613 int i;
1614 unsigned char Kxoripad[65];
1615 unsigned char Kxoropad[65];
1616 size_t pwlen;
1618 cipher = purple_ciphers_find_cipher("md5");
1619 ctx = purple_cipher_context_new(cipher, NULL);
1621 memset(Kxoripad,0,sizeof(Kxoripad));
1622 memset(Kxoropad,0,sizeof(Kxoropad));
1624 pwlen=strlen(passwd);
1625 if (pwlen>64) {
1626 purple_cipher_context_append(ctx, (const guchar *)passwd, strlen(passwd));
1627 purple_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL);
1628 pwlen=16;
1629 } else {
1630 memcpy(Kxoripad, passwd, pwlen);
1632 memcpy(Kxoropad,Kxoripad,pwlen);
1634 for (i=0;i<64;i++) {
1635 Kxoripad[i]^=0x36;
1636 Kxoropad[i]^=0x5c;
1639 purple_cipher_context_reset(ctx, NULL);
1640 purple_cipher_context_append(ctx, Kxoripad, 64);
1641 purple_cipher_context_append(ctx, challenge, challen);
1642 purple_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL);
1644 purple_cipher_context_reset(ctx, NULL);
1645 purple_cipher_context_append(ctx, Kxoropad, 64);
1646 purple_cipher_context_append(ctx, Kxoripad, 16);
1647 purple_cipher_context_digest(ctx, 16, response, NULL);
1649 purple_cipher_context_destroy(ctx);
1652 static void
1653 s5_readchap(gpointer data, gint source, PurpleInputCondition cond);
1656 * Return how many bytes we processed
1657 * -1 means we've shouldn't keep reading from the buffer
1659 static gssize
1660 s5_parse_chap_msg(PurpleProxyConnectData *connect_data)
1662 guchar *buf, *cmdbuf = connect_data->read_buffer;
1663 int len, navas, currentav;
1665 purple_debug_misc("socks5 proxy", "Reading CHAP message: %x\n", *cmdbuf);
1667 if (*cmdbuf != 0x01) {
1668 purple_proxy_connect_data_disconnect(connect_data,
1669 _("Received invalid data on connection with server"));
1670 return -1;
1672 cmdbuf++;
1674 navas = *cmdbuf;
1676 purple_debug_misc("socks5 proxy", "Expecting %d attribute(s).\n", navas);
1678 cmdbuf++;
1680 for (currentav = 0; currentav < navas; currentav++) {
1682 len = connect_data->read_len - (cmdbuf - connect_data->read_buffer);
1683 /* We don't have enough data to even know how long the next attribute is,
1684 * or we don't have the full length of the next attribute. */
1685 if (len < 2 || len < (cmdbuf[1] + 2)) {
1686 /* Clear out the attributes that have been read - decrease the attribute count */
1687 connect_data->read_buffer[1] = navas - currentav;
1688 /* Move the unprocessed data into the first attribute position */
1689 memmove((connect_data->read_buffer + 2), cmdbuf, len);
1690 /* Decrease the read count accordingly */
1691 connect_data->read_len = len + 2;
1693 purple_debug_info("socks5 proxy", "Need more data to retrieve attribute %d.\n", currentav);
1695 return -1;
1698 buf = cmdbuf + 2;
1700 if (cmdbuf[1] == 0) {
1701 purple_debug_error("socks5 proxy", "Attribute %x Value length of 0; ignoring.\n", cmdbuf[0]);
1702 cmdbuf = buf;
1703 continue;
1706 switch (cmdbuf[0]) {
1707 case 0x00:
1708 purple_debug_info("socks5 proxy", "Received STATUS of %x\n", buf[0]);
1709 /* Did auth work? */
1710 if (buf[0] == 0x00) {
1711 purple_input_remove(connect_data->inpa);
1712 connect_data->inpa = 0;
1713 g_free(connect_data->read_buffer);
1714 connect_data->read_buffer = NULL;
1715 /* Success */
1716 s5_sendconnect(connect_data, connect_data->fd);
1717 } else {
1718 /* Failure */
1719 purple_debug_warning("proxy",
1720 "socks5 CHAP authentication "
1721 "failed. Disconnecting...");
1722 purple_proxy_connect_data_disconnect(connect_data,
1723 _("Authentication failed"));
1725 return -1;
1726 case 0x01:
1727 /* We've already validated that cmdbuf[1] is sane. */
1728 purple_debug_info("socks5 proxy", "Received TEXT-MESSAGE of '%.*s'\n", (int) cmdbuf[1], buf);
1729 break;
1730 case 0x03:
1731 purple_debug_info("socks5 proxy", "Received CHALLENGE\n");
1732 /* Server wants our credentials */
1734 connect_data->write_buf_len = 16 + 4;
1735 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1736 connect_data->written_len = 0;
1738 hmacmd5_chap(buf, cmdbuf[1],
1739 purple_proxy_info_get_password(connect_data->gpi),
1740 connect_data->write_buffer + 4);
1741 /* TODO: What about USER-IDENTITY? */
1742 connect_data->write_buffer[0] = 0x01;
1743 connect_data->write_buffer[1] = 0x01;
1744 connect_data->write_buffer[2] = 0x04;
1745 connect_data->write_buffer[3] = 0x10;
1747 purple_input_remove(connect_data->inpa);
1748 g_free(connect_data->read_buffer);
1749 connect_data->read_buffer = NULL;
1751 connect_data->read_cb = s5_readchap;
1753 connect_data->inpa = purple_input_add(connect_data->fd,
1754 PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1756 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1757 return -1;
1758 case 0x11:
1759 purple_debug_info("socks5 proxy", "Received ALGORIGTHMS of %x\n", buf[0]);
1760 /* Server wants to select an algorithm */
1761 if (buf[0] != 0x85) {
1762 /* Only currently support HMAC-MD5 */
1763 purple_debug_warning("proxy",
1764 "Server tried to select an "
1765 "algorithm that we did not advertise "
1766 "as supporting. This is a violation "
1767 "of the socks5 CHAP specification. "
1768 "Disconnecting...");
1769 purple_proxy_connect_data_disconnect(connect_data,
1770 _("Received invalid data on connection with server"));
1771 return -1;
1773 break;
1774 default:
1775 purple_debug_info("socks5 proxy", "Received unused command %x, length=%d\n", cmdbuf[0], cmdbuf[1]);
1777 cmdbuf = buf + cmdbuf[1];
1780 return (cmdbuf - connect_data->read_buffer);
1783 static void
1784 s5_readchap(gpointer data, gint source, PurpleInputCondition cond)
1786 gssize msg_ret;
1787 PurpleProxyConnectData *connect_data = data;
1788 int len;
1790 purple_debug(PURPLE_DEBUG_INFO, "socks5 proxy", "Got CHAP response.\n");
1792 if (connect_data->read_buffer == NULL) {
1793 /* A big enough butfer to read the message header (2 bytes) and at least one complete attribute and value (1 + 1 + 255). */
1794 connect_data->read_buf_len = 259;
1795 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1796 connect_data->read_len = 0;
1799 if (connect_data->read_buf_len - connect_data->read_len == 0) {
1800 /*If the stuff below is right, this shouldn't be possible. */
1801 purple_debug_error("socks5 proxy", "This is about to suck because the read buffer is full (shouldn't happen).\n");
1804 len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
1805 connect_data->read_buf_len - connect_data->read_len);
1807 if (len == 0) {
1808 purple_proxy_connect_data_disconnect(connect_data,
1809 _("Server closed the connection"));
1810 return;
1813 if (len < 0) {
1814 if (errno == EAGAIN)
1815 /* No worries */
1816 return;
1818 /* Error! */
1819 purple_proxy_connect_data_disconnect_formatted(connect_data,
1820 _("Lost connection with server: %s"), g_strerror(errno));
1821 return;
1824 connect_data->read_len += len;
1826 /* We may have read more than one message into the buffer, we need to make sure to process them all */
1827 while (1) {
1829 /* We need more to be able to read this message */
1830 if (connect_data->read_len < 2)
1831 return;
1833 msg_ret = s5_parse_chap_msg(connect_data);
1835 if (msg_ret < 0)
1836 return;
1838 /* See if we have another message already in the buffer */
1839 if ((len = connect_data->read_len - msg_ret) > 0) {
1841 /* Move on to the next message */
1842 memmove(connect_data->read_buffer, connect_data->read_buffer + msg_ret, len);
1843 /* Decrease the read count accordingly */
1844 connect_data->read_len = len;
1846 /* Try to read the message that connect_data->read_buffer now points to */
1847 continue;
1850 break;
1853 /* Fell through. We ran out of CHAP events to process, but haven't
1854 * succeeded or failed authentication - there may be more to come.
1855 * If this is the case, come straight back here. */
1857 purple_debug_info("socks5 proxy", "Waiting for another message from which to read CHAP info.\n");
1859 /* We've processed all the available attributes, so get ready for a whole new message */
1860 g_free(connect_data->read_buffer);
1861 connect_data->read_buffer = NULL;
1864 static void
1865 s5_canread(gpointer data, gint source, PurpleInputCondition cond)
1867 PurpleProxyConnectData *connect_data = data;
1868 int len;
1870 if (connect_data->read_buffer == NULL) {
1871 connect_data->read_buf_len = 2;
1872 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1873 connect_data->read_len = 0;
1876 purple_debug_info("socks5 proxy", "Able to read.\n");
1878 len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
1879 connect_data->read_buf_len - connect_data->read_len);
1881 if (len == 0)
1883 purple_proxy_connect_data_disconnect(connect_data,
1884 _("Server closed the connection"));
1885 return;
1888 if (len < 0)
1890 if (errno == EAGAIN)
1891 /* No worries */
1892 return;
1894 /* Error! */
1895 purple_proxy_connect_data_disconnect_formatted(connect_data,
1896 _("Lost connection with server: %s"), g_strerror(errno));
1897 return;
1900 connect_data->read_len += len;
1901 if (connect_data->read_len < 2)
1902 return;
1904 purple_input_remove(connect_data->inpa);
1905 connect_data->inpa = 0;
1907 if ((connect_data->read_buffer[0] != 0x05) || (connect_data->read_buffer[1] == 0xff)) {
1908 purple_proxy_connect_data_disconnect(connect_data,
1909 _("Received invalid data on connection with server"));
1910 return;
1913 if (connect_data->read_buffer[1] == 0x02) {
1914 size_t i, j;
1915 const char *u, *p;
1917 u = purple_proxy_info_get_username(connect_data->gpi);
1918 p = purple_proxy_info_get_password(connect_data->gpi);
1920 i = (u == NULL) ? 0 : strlen(u);
1921 j = (p == NULL) ? 0 : strlen(p);
1923 connect_data->write_buf_len = 1 + 1 + i + 1 + j;
1924 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1925 connect_data->written_len = 0;
1927 connect_data->write_buffer[0] = 0x01; /* version 1 */
1928 connect_data->write_buffer[1] = i;
1929 if (u != NULL)
1930 memcpy(connect_data->write_buffer + 2, u, i);
1931 connect_data->write_buffer[2 + i] = j;
1932 if (p != NULL)
1933 memcpy(connect_data->write_buffer + 2 + i + 1, p, j);
1935 g_free(connect_data->read_buffer);
1936 connect_data->read_buffer = NULL;
1938 connect_data->read_cb = s5_readauth;
1940 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE,
1941 proxy_do_write, connect_data);
1943 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1945 return;
1946 } else if (connect_data->read_buffer[1] == 0x03) {
1947 size_t userlen;
1948 userlen = strlen(purple_proxy_info_get_username(connect_data->gpi));
1950 connect_data->write_buf_len = 7 + userlen;
1951 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1952 connect_data->written_len = 0;
1954 connect_data->write_buffer[0] = 0x01;
1955 connect_data->write_buffer[1] = 0x02;
1956 connect_data->write_buffer[2] = 0x11;
1957 connect_data->write_buffer[3] = 0x01;
1958 connect_data->write_buffer[4] = 0x85;
1959 connect_data->write_buffer[5] = 0x02;
1960 connect_data->write_buffer[6] = userlen;
1961 memcpy(connect_data->write_buffer + 7,
1962 purple_proxy_info_get_username(connect_data->gpi), userlen);
1964 g_free(connect_data->read_buffer);
1965 connect_data->read_buffer = NULL;
1967 connect_data->read_cb = s5_readchap;
1969 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE,
1970 proxy_do_write, connect_data);
1972 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1974 return;
1975 } else {
1976 g_free(connect_data->read_buffer);
1977 connect_data->read_buffer = NULL;
1979 s5_sendconnect(connect_data, connect_data->fd);
1983 static void
1984 s5_canwrite(gpointer data, gint source, PurpleInputCondition cond)
1986 unsigned char buf[5];
1987 int i;
1988 PurpleProxyConnectData *connect_data = data;
1989 int error = ETIMEDOUT;
1990 int ret;
1992 purple_debug_info("socks5 proxy", "Connected.\n");
1994 if (connect_data->inpa > 0)
1996 purple_input_remove(connect_data->inpa);
1997 connect_data->inpa = 0;
2000 ret = purple_input_get_error(connect_data->fd, &error);
2001 if ((ret != 0) || (error != 0))
2003 if (ret != 0)
2004 error = errno;
2005 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
2006 return;
2009 i = 0;
2010 buf[0] = 0x05; /* SOCKS version 5 */
2012 if (purple_proxy_info_get_username(connect_data->gpi) != NULL) {
2013 buf[1] = 0x03; /* three methods */
2014 buf[2] = 0x00; /* no authentication */
2015 buf[3] = 0x03; /* CHAP authentication */
2016 buf[4] = 0x02; /* username/password authentication */
2017 i = 5;
2019 else {
2020 buf[1] = 0x01;
2021 buf[2] = 0x00;
2022 i = 3;
2025 connect_data->write_buf_len = i;
2026 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
2027 memcpy(connect_data->write_buffer, buf, i);
2029 connect_data->read_cb = s5_canread;
2031 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
2032 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
2035 static void
2036 proxy_connect_socks5(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
2038 int flags;
2040 purple_debug_info("proxy",
2041 "Connecting to %s:%d via %s:%d using SOCKS5\n",
2042 connect_data->host, connect_data->port,
2043 purple_proxy_info_get_host(connect_data->gpi),
2044 purple_proxy_info_get_port(connect_data->gpi));
2046 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
2047 if (connect_data->fd < 0)
2049 purple_proxy_connect_data_disconnect_formatted(connect_data,
2050 _("Unable to create socket: %s"), g_strerror(errno));
2051 return;
2054 flags = fcntl(connect_data->fd, F_GETFL);
2055 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
2056 #ifndef _WIN32
2057 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
2058 #endif
2060 if (connect(connect_data->fd, addr, addrlen) != 0)
2062 if ((errno == EINPROGRESS) || (errno == EINTR))
2064 purple_debug_info("socks5 proxy", "Connection in progress\n");
2065 connect_data->inpa = purple_input_add(connect_data->fd,
2066 PURPLE_INPUT_WRITE, s5_canwrite, connect_data);
2068 else
2070 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
2073 else
2075 purple_debug_info("proxy", "Connected immediately.\n");
2077 s5_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
2082 * This function attempts to connect to the next IP address in the list
2083 * of IP addresses returned to us by purple_dnsquery_a() and attemps
2084 * to connect to each one. This is called after the hostname is
2085 * resolved, and each time a connection attempt fails (assuming there
2086 * is another IP address to try).
2088 #ifndef INET6_ADDRSTRLEN
2089 #define INET6_ADDRSTRLEN 46
2090 #endif
2092 static void try_connect(PurpleProxyConnectData *connect_data)
2094 socklen_t addrlen;
2095 struct sockaddr *addr;
2096 char ipaddr[INET6_ADDRSTRLEN];
2098 addrlen = GPOINTER_TO_INT(connect_data->hosts->data);
2099 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
2100 addr = connect_data->hosts->data;
2101 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
2102 #ifdef HAVE_INET_NTOP
2103 inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr,
2104 ipaddr, sizeof(ipaddr));
2105 #else
2106 memcpy(ipaddr, inet_ntoa(((struct sockaddr_in *)addr)->sin_addr),
2107 sizeof(ipaddr));
2108 #endif
2109 purple_debug_info("proxy", "Attempting connection to %s\n", ipaddr);
2111 if (connect_data->socket_type == SOCK_DGRAM) {
2112 proxy_connect_udp_none(connect_data, addr, addrlen);
2113 g_free(addr);
2114 return;
2117 switch (purple_proxy_info_get_type(connect_data->gpi)) {
2118 case PURPLE_PROXY_NONE:
2119 proxy_connect_none(connect_data, addr, addrlen);
2120 break;
2122 case PURPLE_PROXY_HTTP:
2123 proxy_connect_http(connect_data, addr, addrlen);
2124 break;
2126 case PURPLE_PROXY_SOCKS4:
2127 proxy_connect_socks4(connect_data, addr, addrlen);
2128 break;
2130 case PURPLE_PROXY_SOCKS5:
2131 proxy_connect_socks5(connect_data, addr, addrlen);
2132 break;
2134 case PURPLE_PROXY_USE_ENVVAR:
2135 proxy_connect_http(connect_data, addr, addrlen);
2136 break;
2138 default:
2139 break;
2142 g_free(addr);
2145 static void
2146 connection_host_resolved(GSList *hosts, gpointer data,
2147 const char *error_message)
2149 PurpleProxyConnectData *connect_data;
2151 connect_data = data;
2152 connect_data->query_data = NULL;
2154 if (error_message != NULL)
2156 purple_proxy_connect_data_disconnect(connect_data, error_message);
2157 return;
2160 if (hosts == NULL)
2162 purple_proxy_connect_data_disconnect(connect_data, _("Unable to resolve hostname"));
2163 return;
2166 connect_data->hosts = hosts;
2168 try_connect(connect_data);
2171 PurpleProxyInfo *
2172 purple_proxy_get_setup(PurpleAccount *account)
2174 PurpleProxyInfo *gpi = NULL;
2175 const gchar *tmp;
2177 /* This is used as a fallback so we don't overwrite the selected proxy type */
2178 static PurpleProxyInfo *tmp_none_proxy_info = NULL;
2179 if (!tmp_none_proxy_info) {
2180 tmp_none_proxy_info = purple_proxy_info_new();
2181 purple_proxy_info_set_type(tmp_none_proxy_info, PURPLE_PROXY_NONE);
2184 if (account && purple_account_get_proxy_info(account) != NULL) {
2185 gpi = purple_account_get_proxy_info(account);
2186 if (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_GLOBAL)
2187 gpi = NULL;
2189 if (gpi == NULL) {
2190 if (purple_running_gnome())
2191 gpi = purple_gnome_proxy_get_info();
2192 else
2193 gpi = purple_global_proxy_get_info();
2196 if (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR) {
2197 if ((tmp = g_getenv("HTTP_PROXY")) != NULL ||
2198 (tmp = g_getenv("http_proxy")) != NULL ||
2199 (tmp = g_getenv("HTTPPROXY")) != NULL) {
2200 char *proxyhost, *proxyuser, *proxypasswd;
2201 int proxyport;
2203 /* http_proxy-format:
2204 * export http_proxy="http://user:passwd@your.proxy.server:port/"
2206 if(purple_url_parse(tmp, &proxyhost, &proxyport, NULL, &proxyuser, &proxypasswd)) {
2207 purple_proxy_info_set_host(gpi, proxyhost);
2208 g_free(proxyhost);
2210 purple_proxy_info_set_username(gpi, proxyuser);
2211 g_free(proxyuser);
2213 purple_proxy_info_set_password(gpi, proxypasswd);
2214 g_free(proxypasswd);
2216 /* only for backward compatibility */
2217 if (proxyport == 80 &&
2218 ((tmp = g_getenv("HTTP_PROXY_PORT")) != NULL ||
2219 (tmp = g_getenv("http_proxy_port")) != NULL ||
2220 (tmp = g_getenv("HTTPPROXYPORT")) != NULL))
2221 proxyport = atoi(tmp);
2223 purple_proxy_info_set_port(gpi, proxyport);
2225 /* XXX: Do we want to skip this step if user/password were part of url? */
2226 if ((tmp = g_getenv("HTTP_PROXY_USER")) != NULL ||
2227 (tmp = g_getenv("http_proxy_user")) != NULL ||
2228 (tmp = g_getenv("HTTPPROXYUSER")) != NULL)
2229 purple_proxy_info_set_username(gpi, tmp);
2231 if ((tmp = g_getenv("HTTP_PROXY_PASS")) != NULL ||
2232 (tmp = g_getenv("http_proxy_pass")) != NULL ||
2233 (tmp = g_getenv("HTTPPROXYPASS")) != NULL)
2234 purple_proxy_info_set_password(gpi, tmp);
2237 } else {
2238 #ifdef _WIN32
2239 PurpleProxyInfo *wgpi;
2240 if ((wgpi = purple_win32_proxy_get_info()) != NULL)
2241 return wgpi;
2242 #endif
2243 /* no proxy environment variable found, don't use a proxy */
2244 purple_debug_info("proxy", "No environment settings found, not using a proxy\n");
2245 gpi = tmp_none_proxy_info;
2250 return gpi;
2253 PurpleProxyConnectData *
2254 purple_proxy_connect(void *handle, PurpleAccount *account,
2255 const char *host, int port,
2256 PurpleProxyConnectFunction connect_cb, gpointer data)
2258 const char *connecthost = host;
2259 int connectport = port;
2260 PurpleProxyConnectData *connect_data;
2262 g_return_val_if_fail(host != NULL, NULL);
2263 g_return_val_if_fail(port > 0, NULL);
2264 g_return_val_if_fail(connect_cb != NULL, NULL);
2266 connect_data = g_new0(PurpleProxyConnectData, 1);
2267 connect_data->fd = -1;
2268 connect_data->socket_type = SOCK_STREAM;
2269 connect_data->handle = handle;
2270 connect_data->connect_cb = connect_cb;
2271 connect_data->data = data;
2272 connect_data->host = g_strdup(host);
2273 connect_data->port = port;
2274 connect_data->gpi = purple_proxy_get_setup(account);
2276 if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) &&
2277 (purple_proxy_info_get_host(connect_data->gpi) == NULL ||
2278 purple_proxy_info_get_port(connect_data->gpi) <= 0)) {
2280 purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid."));
2281 purple_proxy_connect_data_destroy(connect_data);
2282 return NULL;
2285 switch (purple_proxy_info_get_type(connect_data->gpi))
2287 case PURPLE_PROXY_NONE:
2288 break;
2290 case PURPLE_PROXY_HTTP:
2291 case PURPLE_PROXY_SOCKS4:
2292 case PURPLE_PROXY_SOCKS5:
2293 case PURPLE_PROXY_USE_ENVVAR:
2294 connecthost = purple_proxy_info_get_host(connect_data->gpi);
2295 connectport = purple_proxy_info_get_port(connect_data->gpi);
2296 break;
2298 default:
2299 purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n",
2300 purple_proxy_info_get_type(connect_data->gpi));
2301 purple_proxy_connect_data_destroy(connect_data);
2302 return NULL;
2305 connect_data->query_data = purple_dnsquery_a(connecthost,
2306 connectport, connection_host_resolved, connect_data);
2307 if (connect_data->query_data == NULL)
2309 purple_debug_error("proxy", "dns query failed unexpectedly.\n");
2310 purple_proxy_connect_data_destroy(connect_data);
2311 return NULL;
2314 handles = g_slist_prepend(handles, connect_data);
2316 return connect_data;
2319 PurpleProxyConnectData *
2320 purple_proxy_connect_udp(void *handle, PurpleAccount *account,
2321 const char *host, int port,
2322 PurpleProxyConnectFunction connect_cb, gpointer data)
2324 const char *connecthost = host;
2325 int connectport = port;
2326 PurpleProxyConnectData *connect_data;
2328 g_return_val_if_fail(host != NULL, NULL);
2329 g_return_val_if_fail(port > 0, NULL);
2330 g_return_val_if_fail(connect_cb != NULL, NULL);
2332 connect_data = g_new0(PurpleProxyConnectData, 1);
2333 connect_data->fd = -1;
2334 connect_data->socket_type = SOCK_DGRAM;
2335 connect_data->handle = handle;
2336 connect_data->connect_cb = connect_cb;
2337 connect_data->data = data;
2338 connect_data->host = g_strdup(host);
2339 connect_data->port = port;
2340 connect_data->gpi = purple_proxy_get_setup(account);
2342 if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) &&
2343 (purple_proxy_info_get_host(connect_data->gpi) == NULL ||
2344 purple_proxy_info_get_port(connect_data->gpi) <= 0)) {
2346 purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid."));
2347 purple_proxy_connect_data_destroy(connect_data);
2348 return NULL;
2351 switch (purple_proxy_info_get_type(connect_data->gpi))
2353 case PURPLE_PROXY_NONE:
2354 break;
2356 case PURPLE_PROXY_HTTP:
2357 case PURPLE_PROXY_SOCKS4:
2358 case PURPLE_PROXY_SOCKS5:
2359 case PURPLE_PROXY_USE_ENVVAR:
2360 purple_debug_info("proxy", "Ignoring Proxy type (%d) for UDP.\n",
2361 purple_proxy_info_get_type(connect_data->gpi));
2362 break;
2364 default:
2365 purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n",
2366 purple_proxy_info_get_type(connect_data->gpi));
2367 purple_proxy_connect_data_destroy(connect_data);
2368 return NULL;
2371 connect_data->query_data = purple_dnsquery_a(connecthost,
2372 connectport, connection_host_resolved, connect_data);
2373 if (connect_data->query_data == NULL)
2375 purple_proxy_connect_data_destroy(connect_data);
2376 return NULL;
2379 handles = g_slist_prepend(handles, connect_data);
2381 return connect_data;
2385 * Combine some of this code with purple_proxy_connect()
2387 PurpleProxyConnectData *
2388 purple_proxy_connect_socks5(void *handle, PurpleProxyInfo *gpi,
2389 const char *host, int port,
2390 PurpleProxyConnectFunction connect_cb,
2391 gpointer data)
2393 PurpleProxyConnectData *connect_data;
2395 g_return_val_if_fail(host != NULL, NULL);
2396 g_return_val_if_fail(port >= 0, NULL);
2397 g_return_val_if_fail(connect_cb != NULL, NULL);
2399 connect_data = g_new0(PurpleProxyConnectData, 1);
2400 connect_data->fd = -1;
2401 connect_data->socket_type = SOCK_STREAM;
2402 connect_data->handle = handle;
2403 connect_data->connect_cb = connect_cb;
2404 connect_data->data = data;
2405 connect_data->host = g_strdup(host);
2406 connect_data->port = port;
2407 connect_data->gpi = gpi;
2409 connect_data->query_data =
2410 purple_dnsquery_a(purple_proxy_info_get_host(gpi),
2411 purple_proxy_info_get_port(gpi),
2412 connection_host_resolved, connect_data);
2413 if (connect_data->query_data == NULL)
2415 purple_proxy_connect_data_destroy(connect_data);
2416 return NULL;
2419 handles = g_slist_prepend(handles, connect_data);
2421 return connect_data;
2424 void
2425 purple_proxy_connect_cancel(PurpleProxyConnectData *connect_data)
2427 purple_proxy_connect_data_disconnect(connect_data, NULL);
2428 purple_proxy_connect_data_destroy(connect_data);
2431 void
2432 purple_proxy_connect_cancel_with_handle(void *handle)
2434 GSList *l, *l_next;
2436 for (l = handles; l != NULL; l = l_next) {
2437 PurpleProxyConnectData *connect_data = l->data;
2439 l_next = l->next;
2441 if (connect_data->handle == handle)
2442 purple_proxy_connect_cancel(connect_data);
2446 static void
2447 proxy_pref_cb(const char *name, PurplePrefType type,
2448 gconstpointer value, gpointer data)
2450 PurpleProxyInfo *info = purple_global_proxy_get_info();
2452 if (purple_strequal(name, "/purple/proxy/type")) {
2453 int proxytype;
2454 const char *type = value;
2456 if (purple_strequal(type, "none"))
2457 proxytype = PURPLE_PROXY_NONE;
2458 else if (purple_strequal(type, "http"))
2459 proxytype = PURPLE_PROXY_HTTP;
2460 else if (purple_strequal(type, "socks4"))
2461 proxytype = PURPLE_PROXY_SOCKS4;
2462 else if (purple_strequal(type, "socks5"))
2463 proxytype = PURPLE_PROXY_SOCKS5;
2464 else if (purple_strequal(type, "envvar"))
2465 proxytype = PURPLE_PROXY_USE_ENVVAR;
2466 else
2467 proxytype = -1;
2469 purple_proxy_info_set_type(info, proxytype);
2470 } else if (purple_strequal(name, "/purple/proxy/host"))
2471 purple_proxy_info_set_host(info, value);
2472 else if (purple_strequal(name, "/purple/proxy/port"))
2473 purple_proxy_info_set_port(info, GPOINTER_TO_INT(value));
2474 else if (purple_strequal(name, "/purple/proxy/username"))
2475 purple_proxy_info_set_username(info, value);
2476 else if (purple_strequal(name, "/purple/proxy/password"))
2477 purple_proxy_info_set_password(info, value);
2480 void *
2481 purple_proxy_get_handle()
2483 static int handle;
2485 return &handle;
2488 void
2489 purple_proxy_init(void)
2491 void *handle;
2493 /* Initialize a default proxy info struct. */
2494 global_proxy_info = purple_proxy_info_new();
2496 /* Proxy */
2497 purple_prefs_add_none("/purple/proxy");
2498 purple_prefs_add_string("/purple/proxy/type", "none");
2499 purple_prefs_add_string("/purple/proxy/host", "");
2500 purple_prefs_add_int("/purple/proxy/port", 0);
2501 purple_prefs_add_string("/purple/proxy/username", "");
2502 purple_prefs_add_string("/purple/proxy/password", "");
2503 purple_prefs_add_bool("/purple/proxy/socks4_remotedns", FALSE);
2505 /* Setup callbacks for the preferences. */
2506 handle = purple_proxy_get_handle();
2507 purple_prefs_connect_callback(handle, "/purple/proxy/type", proxy_pref_cb,
2508 NULL);
2509 purple_prefs_connect_callback(handle, "/purple/proxy/host", proxy_pref_cb,
2510 NULL);
2511 purple_prefs_connect_callback(handle, "/purple/proxy/port", proxy_pref_cb,
2512 NULL);
2513 purple_prefs_connect_callback(handle, "/purple/proxy/username",
2514 proxy_pref_cb, NULL);
2515 purple_prefs_connect_callback(handle, "/purple/proxy/password",
2516 proxy_pref_cb, NULL);
2518 /* Load the initial proxy settings */
2519 purple_prefs_trigger_callback("/purple/proxy/type");
2520 purple_prefs_trigger_callback("/purple/proxy/host");
2521 purple_prefs_trigger_callback("/purple/proxy/port");
2522 purple_prefs_trigger_callback("/purple/proxy/username");
2523 purple_prefs_trigger_callback("/purple/proxy/password");
2526 void
2527 purple_proxy_uninit(void)
2529 while (handles != NULL)
2531 purple_proxy_connect_data_disconnect(handles->data, NULL);
2532 purple_proxy_connect_data_destroy(handles->data);
2535 purple_prefs_disconnect_by_handle(purple_proxy_get_handle());
2537 purple_proxy_info_destroy(global_proxy_info);
2538 global_proxy_info = NULL;