These changes are reuired to make the Windows installer build.
[pidgin-git.git] / libpurple / proxy.c
blob63b0cc86887037ca34f005f266d8bd1ef351b423
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';
385 d++;
386 if (*d)
387 sscanf(d, "%d", &port);
389 if (port == 0)
390 port = default_port;
393 purple_proxy_info_set_host(info, host);
394 purple_proxy_info_set_port(info, port);
397 static PurpleProxyInfo *
398 purple_win32_proxy_get_info(void)
400 static LPFNWINHTTPGETIEPROXYCONFIG MyWinHttpGetIEProxyConfig = NULL;
401 static gboolean loaded = FALSE;
402 static PurpleProxyInfo info = {0, NULL, 0, NULL, NULL};
404 WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_proxy_config;
406 if (!loaded) {
407 loaded = TRUE;
408 MyWinHttpGetIEProxyConfig = (LPFNWINHTTPGETIEPROXYCONFIG)
409 wpurple_find_and_loadproc("winhttp.dll", "WinHttpGetIEProxyConfigForCurrentUser");
410 if (!MyWinHttpGetIEProxyConfig)
411 purple_debug_warning("proxy", "Unable to read Windows Proxy Settings.\n");
414 if (!MyWinHttpGetIEProxyConfig)
415 return NULL;
417 ZeroMemory(&ie_proxy_config, sizeof(ie_proxy_config));
418 if (!MyWinHttpGetIEProxyConfig(&ie_proxy_config)) {
419 purple_debug_error("proxy", "Error reading Windows Proxy Settings(%lu).\n", GetLastError());
420 return NULL;
423 /* We can't do much if it is autodetect*/
424 if (ie_proxy_config.fAutoDetect) {
425 purple_debug_error("proxy", "Windows Proxy Settings set to autodetect (not supported).\n");
427 /* TODO: For 3.0.0 we'll revisit this (maybe)*/
429 return NULL;
431 } else if (ie_proxy_config.lpszProxy) {
432 gchar *proxy_list = g_utf16_to_utf8(ie_proxy_config.lpszProxy, -1,
433 NULL, NULL, NULL);
435 /* We can't do anything about the bypass list, as we don't have the url */
436 /* TODO: For 3.0.0 we'll revisit this*/
438 /* There are proxy settings for several protocols */
439 if (proxy_list && *proxy_list) {
440 char *specific = NULL, *tmp;
442 /* If there is only a global proxy, which means "HTTP" */
443 if (!strchr(proxy_list, ';') || (specific = g_strstr_len(proxy_list, -1, "http=")) != NULL) {
445 if (specific) {
446 specific += strlen("http=");
447 tmp = strchr(specific, ';');
448 if (tmp)
449 *tmp = '\0';
450 /* specific now points the proxy server (and port) */
451 } else
452 specific = proxy_list;
454 purple_proxy_info_set_type(&info, PURPLE_PROXY_HTTP);
455 _proxy_fill_hostinfo(&info, specific, 80);
456 /* TODO: is there a way to set the username/password? */
457 purple_proxy_info_set_username(&info, NULL);
458 purple_proxy_info_set_password(&info, NULL);
460 purple_debug_info("proxy", "Windows Proxy Settings: HTTP proxy: '%s:%d'.\n",
461 purple_proxy_info_get_host(&info),
462 purple_proxy_info_get_port(&info));
464 } else if ((specific = g_strstr_len(proxy_list, -1, "socks=")) != NULL) {
466 specific += strlen("socks=");
467 tmp = strchr(specific, ';');
468 if (tmp)
469 *tmp = '\0';
470 /* specific now points the proxy server (and port) */
472 purple_proxy_info_set_type(&info, PURPLE_PROXY_SOCKS5);
473 _proxy_fill_hostinfo(&info, specific, 1080);
474 /* TODO: is there a way to set the username/password? */
475 purple_proxy_info_set_username(&info, NULL);
476 purple_proxy_info_set_password(&info, NULL);
478 purple_debug_info("proxy", "Windows Proxy Settings: SOCKS5 proxy: '%s:%d'.\n",
479 purple_proxy_info_get_host(&info),
480 purple_proxy_info_get_port(&info));
482 } else {
484 purple_debug_info("proxy", "Windows Proxy Settings: No supported proxy specified.\n");
486 purple_proxy_info_set_type(&info, PURPLE_PROXY_NONE);
491 /* TODO: Fix API to be able look at proxy bypass settings */
493 g_free(proxy_list);
494 } else {
495 purple_debug_info("proxy", "No Windows proxy set.\n");
496 purple_proxy_info_set_type(&info, PURPLE_PROXY_NONE);
499 if (ie_proxy_config.lpszAutoConfigUrl)
500 GlobalFree(ie_proxy_config.lpszAutoConfigUrl);
501 if (ie_proxy_config.lpszProxy)
502 GlobalFree(ie_proxy_config.lpszProxy);
503 if (ie_proxy_config.lpszProxyBypass)
504 GlobalFree(ie_proxy_config.lpszProxyBypass);
506 return &info;
508 #endif
511 /**************************************************************************
512 * Proxy API
513 **************************************************************************/
516 * Whoever calls this needs to have called
517 * purple_proxy_connect_data_disconnect() beforehand.
519 static void
520 purple_proxy_connect_data_destroy(PurpleProxyConnectData *connect_data)
522 handles = g_slist_remove(handles, connect_data);
524 if (connect_data->query_data != NULL)
525 purple_dnsquery_destroy(connect_data->query_data);
527 while (connect_data->hosts != NULL)
529 /* Discard the length... */
530 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
531 /* Free the address... */
532 g_free(connect_data->hosts->data);
533 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
536 g_free(connect_data->host);
537 g_free(connect_data);
541 * Free all information dealing with a connection attempt and
542 * reset the connect_data to prepare for it to try to connect
543 * to another IP address.
545 * If an error message is passed in, then we know the connection
546 * attempt failed. If the connection attempt failed and
547 * connect_data->hosts is not empty then we try the next IP address.
548 * If the connection attempt failed and we have no more hosts
549 * try try then we call the callback with the given error message,
550 * then destroy the connect_data.
552 * @param error_message An error message explaining why the connection
553 * failed. This will be passed to the callback function
554 * specified in the call to purple_proxy_connect(). If the
555 * connection was successful then pass in null.
557 static void
558 purple_proxy_connect_data_disconnect(PurpleProxyConnectData *connect_data, const gchar *error_message)
560 if (connect_data->inpa > 0)
562 purple_input_remove(connect_data->inpa);
563 connect_data->inpa = 0;
566 if (connect_data->fd >= 0)
568 close(connect_data->fd);
569 connect_data->fd = -1;
572 g_free(connect_data->write_buffer);
573 connect_data->write_buffer = NULL;
575 g_free(connect_data->read_buffer);
576 connect_data->read_buffer = NULL;
578 if (error_message != NULL)
580 purple_debug_error("proxy", "Connection attempt failed: %s\n",
581 error_message);
582 if (connect_data->hosts != NULL)
583 try_connect(connect_data);
584 else
586 /* Everything failed! Tell the originator of the request. */
587 connect_data->connect_cb(connect_data->data, -1, error_message);
588 purple_proxy_connect_data_destroy(connect_data);
594 * This calls purple_proxy_connect_data_disconnect(), but it lets you
595 * specify the error_message using a printf()-like syntax.
597 static void
598 purple_proxy_connect_data_disconnect_formatted(PurpleProxyConnectData *connect_data, const char *format, ...)
600 va_list args;
601 gchar *tmp;
603 va_start(args, format);
604 tmp = g_strdup_vprintf(format, args);
605 va_end(args);
607 purple_proxy_connect_data_disconnect(connect_data, tmp);
608 g_free(tmp);
611 static void
612 purple_proxy_connect_data_connected(PurpleProxyConnectData *connect_data)
614 purple_debug_info("proxy", "Connected to %s:%d.\n",
615 connect_data->host, connect_data->port);
617 connect_data->connect_cb(connect_data->data, connect_data->fd, NULL);
620 * We've passed the file descriptor to the protocol, so it's no longer
621 * our responsibility, and we should be careful not to free it when
622 * we destroy the connect_data.
624 connect_data->fd = -1;
626 purple_proxy_connect_data_disconnect(connect_data, NULL);
627 purple_proxy_connect_data_destroy(connect_data);
630 static void
631 socket_ready_cb(gpointer data, gint source, PurpleInputCondition cond)
633 PurpleProxyConnectData *connect_data = data;
634 int error = 0;
635 int ret;
637 /* If the socket-connected message had already been triggered when connect_data
638 * was destroyed via purple_proxy_connect_cancel(), we may get here with a freed connect_data.
640 if (!PURPLE_PROXY_CONNECT_DATA_IS_VALID(connect_data))
641 return;
643 purple_debug_info("proxy", "Connecting to %s:%d.\n",
644 connect_data->host, connect_data->port);
647 * purple_input_get_error after a non-blocking connect returns -1 if something is
648 * really messed up (bad descriptor, usually). Otherwise, it returns 0 and
649 * error holds what connect would have returned if it blocked until now.
650 * Thus, error == 0 is success, error == EINPROGRESS means "try again",
651 * and anything else is a real error.
653 * (error == EINPROGRESS can happen after a select because the kernel can
654 * be overly optimistic sometimes. select is just a hint that you might be
655 * able to do something.)
657 ret = purple_input_get_error(connect_data->fd, &error);
659 if (ret == 0 && error == EINPROGRESS) {
660 /* No worries - we'll be called again later */
661 /* TODO: Does this ever happen? */
662 purple_debug_info("proxy", "(ret == 0 && error == EINPROGRESS)\n");
663 return;
666 if (ret != 0 || error != 0) {
667 if (ret != 0)
668 error = errno;
669 purple_debug_error("proxy", "Error connecting to %s:%d (%s).\n",
670 connect_data->host, connect_data->port, g_strerror(error));
672 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
673 return;
676 purple_proxy_connect_data_connected(connect_data);
679 static gboolean
680 clean_connect(gpointer data)
682 purple_proxy_connect_data_connected(data);
684 return FALSE;
687 static void
688 proxy_connect_udp_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
690 int flags;
692 purple_debug_info("proxy", "UDP Connecting to %s:%d with no proxy\n",
693 connect_data->host, connect_data->port);
695 connect_data->fd = socket(addr->sa_family, SOCK_DGRAM, 0);
696 if (connect_data->fd < 0)
698 purple_proxy_connect_data_disconnect_formatted(connect_data,
699 _("Unable to create socket: %s"), g_strerror(errno));
700 return;
703 flags = fcntl(connect_data->fd, F_GETFL);
704 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
705 #ifndef _WIN32
706 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
707 #endif
709 if (connect(connect_data->fd, addr, addrlen) != 0)
711 if ((errno == EINPROGRESS) || (errno == EINTR))
713 purple_debug_info("proxy", "UDP Connection in progress\n");
714 connect_data->inpa = purple_input_add(connect_data->fd,
715 PURPLE_INPUT_WRITE, socket_ready_cb, connect_data);
717 else
719 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
722 else
725 * The connection happened IMMEDIATELY... strange, but whatever.
727 int error = ETIMEDOUT;
728 int ret;
730 purple_debug_info("proxy", "UDP Connected immediately.\n");
732 ret = purple_input_get_error(connect_data->fd, &error);
733 if ((ret != 0) || (error != 0))
735 if (ret != 0)
736 error = errno;
737 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
738 return;
742 * We want to call the "connected" callback eventually, but we
743 * don't want to call it before we return, just in case.
745 purple_timeout_add(10, clean_connect, connect_data);
749 static void
750 proxy_connect_none(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
752 int flags;
754 purple_debug_info("proxy", "Connecting to %s:%d with no proxy\n",
755 connect_data->host, connect_data->port);
757 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
758 if (connect_data->fd < 0)
760 purple_proxy_connect_data_disconnect_formatted(connect_data,
761 _("Unable to create socket: %s"), g_strerror(errno));
762 return;
765 flags = fcntl(connect_data->fd, F_GETFL);
766 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
767 #ifndef _WIN32
768 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
769 #endif
771 if (connect(connect_data->fd, addr, addrlen) != 0)
773 if ((errno == EINPROGRESS) || (errno == EINTR))
775 purple_debug_info("proxy", "Connection in progress\n");
776 connect_data->inpa = purple_input_add(connect_data->fd,
777 PURPLE_INPUT_WRITE, socket_ready_cb, connect_data);
779 else
781 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
784 else
787 * The connection happened IMMEDIATELY... strange, but whatever.
789 int error = ETIMEDOUT;
790 int ret;
792 purple_debug_info("proxy", "Connected immediately.\n");
794 ret = purple_input_get_error(connect_data->fd, &error);
795 if ((ret != 0) || (error != 0))
797 if (ret != 0)
798 error = errno;
799 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
800 return;
804 * We want to call the "connected" callback eventually, but we
805 * don't want to call it before we return, just in case.
807 purple_timeout_add(10, clean_connect, connect_data);
812 * This is a utility function used by the HTTP, SOCKS4 and SOCKS5
813 * connect functions. It writes data from a buffer to a socket.
814 * When all the data is written it sets up a watcher to read a
815 * response and call a specified function.
817 static void
818 proxy_do_write(gpointer data, gint source, PurpleInputCondition cond)
820 PurpleProxyConnectData *connect_data;
821 const guchar *request;
822 gsize request_len;
823 int ret;
825 connect_data = data;
826 request = connect_data->write_buffer + connect_data->written_len;
827 request_len = connect_data->write_buf_len - connect_data->written_len;
829 ret = write(connect_data->fd, request, request_len);
830 if (ret <= 0)
832 if (errno == EAGAIN)
833 /* No worries */
834 return;
836 /* Error! */
837 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
838 return;
840 if (ret < request_len) {
841 connect_data->written_len += ret;
842 return;
845 /* We're done writing data! Wait for a response. */
846 g_free(connect_data->write_buffer);
847 connect_data->write_buffer = NULL;
848 purple_input_remove(connect_data->inpa);
849 connect_data->inpa = purple_input_add(connect_data->fd,
850 PURPLE_INPUT_READ, connect_data->read_cb, connect_data);
853 #define HTTP_GOODSTRING "HTTP/1.0 200"
854 #define HTTP_GOODSTRING2 "HTTP/1.1 200"
857 * We're using an HTTP proxy for a non-port 80 tunnel. Read the
858 * response to the CONNECT request.
860 static void
861 http_canread(gpointer data, gint source, PurpleInputCondition cond)
863 int len, headers_len, status = 0;
864 gboolean error;
865 PurpleProxyConnectData *connect_data = data;
866 char *p;
867 gsize max_read;
869 if (connect_data->read_buffer == NULL) {
870 connect_data->read_buf_len = 8192;
871 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
872 connect_data->read_len = 0;
875 p = (char *)connect_data->read_buffer + connect_data->read_len;
876 max_read = connect_data->read_buf_len - connect_data->read_len - 1;
878 len = read(connect_data->fd, p, max_read);
880 if (len == 0) {
881 purple_proxy_connect_data_disconnect(connect_data,
882 _("Server closed the connection"));
883 return;
886 if (len < 0) {
887 if (errno == EAGAIN)
888 /* No worries */
889 return;
891 /* Error! */
892 purple_proxy_connect_data_disconnect_formatted(connect_data,
893 _("Lost connection with server: %s"), g_strerror(errno));
894 return;
897 connect_data->read_len += len;
898 p[len] = '\0';
900 p = g_strstr_len((const gchar *)connect_data->read_buffer,
901 connect_data->read_len, "\r\n\r\n");
902 if (p != NULL) {
903 *p = '\0';
904 headers_len = (p - (char *)connect_data->read_buffer) + 4;
905 } else if(len == max_read)
906 headers_len = len;
907 else
908 return;
910 error = strncmp((const char *)connect_data->read_buffer, "HTTP/", 5) != 0;
911 if (!error) {
912 int major;
913 p = (char *)connect_data->read_buffer + 5;
914 major = strtol(p, &p, 10);
915 error = (major == 0) || (*p != '.');
916 if(!error) {
917 int minor;
918 p++;
919 minor = strtol(p, &p, 10);
920 error = (*p != ' ');
921 if(!error) {
922 p++;
923 status = strtol(p, &p, 10);
924 error = (*p != ' ');
929 /* Read the contents */
930 p = g_strrstr((const gchar *)connect_data->read_buffer, "Content-Length: ");
931 if (p != NULL) {
932 gchar *tmp;
933 int len = 0;
934 char tmpc;
935 p += strlen("Content-Length: ");
936 tmp = strchr(p, '\r');
937 if(tmp)
938 *tmp = '\0';
939 len = atoi(p);
940 if(tmp)
941 *tmp = '\r';
943 /* Compensate for what has already been read */
944 len -= connect_data->read_len - headers_len;
945 /* I'm assuming that we're doing this to prevent the server from
946 complaining / breaking since we don't read the whole page */
947 while (len--) {
948 /* TODO: deal with EAGAIN (and other errors) better */
949 if (read(connect_data->fd, &tmpc, 1) < 0 && errno != EAGAIN)
950 break;
954 if (error) {
955 purple_proxy_connect_data_disconnect_formatted(connect_data,
956 _("Unable to parse response from HTTP proxy: %s"),
957 connect_data->read_buffer);
958 return;
960 else if (status != 200) {
961 purple_debug_error("proxy",
962 "Proxy server replied with:\n%s\n",
963 connect_data->read_buffer);
965 if (status == 407 /* Proxy Auth */) {
966 const char *header;
967 gchar *request;
969 header = g_strrstr((const gchar *)connect_data->read_buffer,
970 "Proxy-Authenticate: NTLM");
971 if (header != NULL) {
972 const char *header_end = header + strlen("Proxy-Authenticate: NTLM");
973 const char *domain = purple_proxy_info_get_username(connect_data->gpi);
974 char *username = NULL, hostname[256];
975 gchar *response;
976 int ret;
978 ret = gethostname(hostname, sizeof(hostname));
979 hostname[sizeof(hostname) - 1] = '\0';
980 if (ret < 0 || hostname[0] == '\0') {
981 purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?");
982 strcpy(hostname, "localhost");
985 if (domain != NULL)
986 username = (char*) strchr(domain, '\\');
987 if (username == NULL) {
988 purple_proxy_connect_data_disconnect_formatted(connect_data,
989 _("HTTP proxy connection error %d"), status);
990 return;
992 *username = '\0';
994 /* Is there a message? */
995 if (*header_end == ' ') {
996 /* Check for Type-2 */
997 char *tmp = (char*) header;
998 guint8 *nonce;
1000 header_end++;
1001 username++;
1002 while(*tmp != '\r' && *tmp != '\0') tmp++;
1003 *tmp = '\0';
1004 nonce = purple_ntlm_parse_type2(header_end, NULL);
1005 response = purple_ntlm_gen_type3(username,
1006 (gchar*) purple_proxy_info_get_password(connect_data->gpi),
1007 hostname,
1008 domain, nonce, NULL);
1009 username--;
1010 } else /* Empty message */
1011 response = purple_ntlm_gen_type1(hostname, domain);
1013 *username = '\\';
1015 request = g_strdup_printf(
1016 "CONNECT %s:%d HTTP/1.1\r\n"
1017 "Host: %s:%d\r\n"
1018 "Proxy-Authorization: NTLM %s\r\n"
1019 "Proxy-Connection: Keep-Alive\r\n\r\n",
1020 connect_data->host, connect_data->port,
1021 connect_data->host, connect_data->port,
1022 response);
1024 g_free(response);
1026 } else if (g_strrstr((const char *)connect_data->read_buffer, "Proxy-Authenticate: Basic") != NULL) {
1027 gchar *t1, *t2;
1028 const char *username, *password;
1030 username = purple_proxy_info_get_username(connect_data->gpi);
1031 password = purple_proxy_info_get_password(connect_data->gpi);
1033 t1 = g_strdup_printf("%s:%s",
1034 username ? username : "",
1035 password ? password : "");
1036 t2 = purple_base64_encode((guchar *)t1, strlen(t1));
1037 g_free(t1);
1039 request = g_strdup_printf(
1040 "CONNECT %s:%d HTTP/1.1\r\n"
1041 "Host: %s:%d\r\n"
1042 "Proxy-Authorization: Basic %s\r\n",
1043 connect_data->host, connect_data->port,
1044 connect_data->host, connect_data->port,
1045 t2);
1047 g_free(t2);
1049 } else {
1050 purple_proxy_connect_data_disconnect_formatted(connect_data,
1051 _("HTTP proxy connection error %d"), status);
1052 return;
1055 purple_input_remove(connect_data->inpa);
1056 g_free(connect_data->read_buffer);
1057 connect_data->read_buffer = NULL;
1059 connect_data->write_buffer = (guchar *)request;
1060 connect_data->write_buf_len = strlen(request);
1061 connect_data->written_len = 0;
1063 connect_data->read_cb = http_canread;
1065 connect_data->inpa = purple_input_add(connect_data->fd,
1066 PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1068 proxy_do_write(connect_data, connect_data->fd, cond);
1070 return;
1073 if (status == 403) {
1074 /* Forbidden */
1075 purple_proxy_connect_data_disconnect_formatted(connect_data,
1076 _("Access denied: HTTP proxy server forbids port %d tunneling"),
1077 connect_data->port);
1078 } else {
1079 purple_proxy_connect_data_disconnect_formatted(connect_data,
1080 _("HTTP proxy connection error %d"), status);
1082 } else {
1083 purple_input_remove(connect_data->inpa);
1084 connect_data->inpa = 0;
1085 g_free(connect_data->read_buffer);
1086 connect_data->read_buffer = NULL;
1087 purple_debug_info("proxy", "HTTP proxy connection established\n");
1088 purple_proxy_connect_data_connected(connect_data);
1089 return;
1093 static void
1094 http_start_connect_tunneling(PurpleProxyConnectData *connect_data) {
1095 GString *request;
1096 int ret;
1098 purple_debug_info("proxy", "Using CONNECT tunneling for %s:%d\n",
1099 connect_data->host, connect_data->port);
1101 request = g_string_sized_new(4096);
1102 g_string_append_printf(request,
1103 "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\n",
1104 connect_data->host, connect_data->port,
1105 connect_data->host, connect_data->port);
1107 if (purple_proxy_info_get_username(connect_data->gpi) != NULL)
1109 char *t1, *t2, *ntlm_type1;
1110 char hostname[256];
1112 ret = gethostname(hostname, sizeof(hostname));
1113 hostname[sizeof(hostname) - 1] = '\0';
1114 if (ret < 0 || hostname[0] == '\0') {
1115 purple_debug_warning("proxy", "gethostname() failed -- is your hostname set?");
1116 strcpy(hostname, "localhost");
1119 t1 = g_strdup_printf("%s:%s",
1120 purple_proxy_info_get_username(connect_data->gpi),
1121 purple_proxy_info_get_password(connect_data->gpi) ?
1122 purple_proxy_info_get_password(connect_data->gpi) : "");
1123 t2 = purple_base64_encode((const guchar *)t1, strlen(t1));
1124 g_free(t1);
1126 ntlm_type1 = purple_ntlm_gen_type1(hostname, "");
1128 g_string_append_printf(request,
1129 "Proxy-Authorization: Basic %s\r\n"
1130 "Proxy-Authorization: NTLM %s\r\n"
1131 "Proxy-Connection: Keep-Alive\r\n",
1132 t2, ntlm_type1);
1133 g_free(ntlm_type1);
1134 g_free(t2);
1137 g_string_append(request, "\r\n");
1139 connect_data->write_buf_len = request->len;
1140 connect_data->write_buffer = (guchar *)g_string_free(request, FALSE);
1141 connect_data->written_len = 0;
1142 connect_data->read_cb = http_canread;
1144 connect_data->inpa = purple_input_add(connect_data->fd,
1145 PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1146 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1149 static void
1150 http_canwrite(gpointer data, gint source, PurpleInputCondition cond) {
1151 PurpleProxyConnectData *connect_data = data;
1152 int ret, error = ETIMEDOUT;
1154 purple_debug_info("proxy", "Connected to %s:%d.\n",
1155 connect_data->host, connect_data->port);
1157 if (connect_data->inpa > 0) {
1158 purple_input_remove(connect_data->inpa);
1159 connect_data->inpa = 0;
1162 ret = purple_input_get_error(connect_data->fd, &error);
1163 if (ret != 0 || error != 0) {
1164 if (ret != 0)
1165 error = errno;
1166 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
1167 return;
1170 if (connect_data->port == 80) {
1172 * If we're trying to connect to something running on
1173 * port 80 then we assume the traffic using this
1174 * connection is going to be HTTP traffic. If it's
1175 * not then this will fail (uglily). But it's good
1176 * to avoid using the CONNECT method because it's
1177 * not always allowed.
1179 purple_debug_info("proxy", "HTTP proxy connection established\n");
1180 purple_proxy_connect_data_connected(connect_data);
1181 } else {
1182 http_start_connect_tunneling(connect_data);
1187 static void
1188 proxy_connect_http(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
1190 int flags;
1192 purple_debug_info("proxy",
1193 "Connecting to %s:%d via %s:%d using HTTP\n",
1194 connect_data->host, connect_data->port,
1195 (purple_proxy_info_get_host(connect_data->gpi) ? purple_proxy_info_get_host(connect_data->gpi) : "(null)"),
1196 purple_proxy_info_get_port(connect_data->gpi));
1198 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
1199 if (connect_data->fd < 0)
1201 purple_proxy_connect_data_disconnect_formatted(connect_data,
1202 _("Unable to create socket: %s"), g_strerror(errno));
1203 return;
1206 flags = fcntl(connect_data->fd, F_GETFL);
1207 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
1208 #ifndef _WIN32
1209 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
1210 #endif
1212 if (connect(connect_data->fd, addr, addrlen) != 0) {
1213 if (errno == EINPROGRESS || errno == EINTR) {
1214 purple_debug_info("proxy", "Connection in progress\n");
1216 connect_data->inpa = purple_input_add(connect_data->fd,
1217 PURPLE_INPUT_WRITE, http_canwrite, connect_data);
1218 } else
1219 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
1220 } else {
1221 purple_debug_info("proxy", "Connected immediately.\n");
1223 http_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1227 static void
1228 s4_canread(gpointer data, gint source, PurpleInputCondition cond)
1230 PurpleProxyConnectData *connect_data = data;
1231 guchar *buf;
1232 int len, max_read;
1234 /* This is really not going to block under normal circumstances, but to
1235 * be correct, we deal with the unlikely scenario */
1237 if (connect_data->read_buffer == NULL) {
1238 connect_data->read_buf_len = 12;
1239 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1240 connect_data->read_len = 0;
1243 buf = connect_data->read_buffer + connect_data->read_len;
1244 max_read = connect_data->read_buf_len - connect_data->read_len;
1246 len = read(connect_data->fd, buf, max_read);
1248 if ((len < 0 && errno == EAGAIN) || (len > 0 && len + connect_data->read_len < 4))
1249 return;
1250 else if (len + connect_data->read_len >= 4) {
1251 if (connect_data->read_buffer[1] == 90) {
1252 purple_proxy_connect_data_connected(connect_data);
1253 return;
1257 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
1260 static void
1261 s4_host_resolved(GSList *hosts, gpointer data, const char *error_message)
1263 PurpleProxyConnectData *connect_data = data;
1264 unsigned char packet[9];
1265 struct sockaddr *addr;
1267 connect_data->query_data = NULL;
1269 if (error_message != NULL) {
1270 purple_proxy_connect_data_disconnect(connect_data, error_message);
1271 return;
1274 if (hosts == NULL) {
1275 purple_proxy_connect_data_disconnect_formatted(connect_data,
1276 _("Error resolving %s"), connect_data->host);
1277 return;
1280 /* Discard the length... */
1281 hosts = g_slist_delete_link(hosts, hosts);
1282 addr = hosts->data;
1283 hosts = g_slist_delete_link(hosts, hosts);
1285 packet[0] = 0x04;
1286 packet[1] = 0x01;
1287 packet[2] = connect_data->port >> 8;
1288 packet[3] = connect_data->port & 0xff;
1289 memcpy(packet + 4, &((struct sockaddr_in *)addr)->sin_addr.s_addr, 4);
1290 packet[8] = 0x00;
1292 g_free(addr);
1294 /* We could try the other hosts, but hopefully that shouldn't be necessary */
1295 while (hosts != NULL) {
1296 /* Discard the length... */
1297 hosts = g_slist_delete_link(hosts, hosts);
1298 /* Free the address... */
1299 g_free(hosts->data);
1300 hosts = g_slist_delete_link(hosts, hosts);
1303 connect_data->write_buffer = g_memdup(packet, sizeof(packet));
1304 connect_data->write_buf_len = sizeof(packet);
1305 connect_data->written_len = 0;
1306 connect_data->read_cb = s4_canread;
1308 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1310 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1313 static void
1314 s4_canwrite(gpointer data, gint source, PurpleInputCondition cond)
1316 PurpleProxyConnectData *connect_data = data;
1317 int error = ETIMEDOUT;
1318 int ret;
1320 purple_debug_info("socks4 proxy", "Connected.\n");
1322 if (connect_data->inpa > 0) {
1323 purple_input_remove(connect_data->inpa);
1324 connect_data->inpa = 0;
1327 ret = purple_input_get_error(connect_data->fd, &error);
1328 if ((ret != 0) || (error != 0)) {
1329 if (ret != 0)
1330 error = errno;
1331 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
1332 return;
1336 * The socks4 spec doesn't include support for doing host name lookups by
1337 * the proxy. Many socks4 servers do this via the "socks4a" extension to
1338 * the protocol. There doesn't appear to be a way to detect if a server
1339 * supports this, so we require that the user set a global option.
1341 if (purple_prefs_get_bool("/purple/proxy/socks4_remotedns")) {
1342 unsigned char packet[9];
1343 int len;
1345 purple_debug_info("socks4 proxy", "Attempting to use remote DNS.\n");
1347 packet[0] = 0x04;
1348 packet[1] = 0x01;
1349 packet[2] = connect_data->port >> 8;
1350 packet[3] = connect_data->port & 0xff;
1351 packet[4] = 0x00;
1352 packet[5] = 0x00;
1353 packet[6] = 0x00;
1354 packet[7] = 0x01;
1355 packet[8] = 0x00;
1357 len = sizeof(packet) + strlen(connect_data->host) + 1;
1359 connect_data->write_buffer = g_malloc0(len);
1360 memcpy(connect_data->write_buffer, packet, sizeof(packet));
1361 memcpy(connect_data->write_buffer + sizeof(packet), connect_data->host, strlen(connect_data->host));
1362 connect_data->write_buf_len = len;
1363 connect_data->written_len = 0;
1364 connect_data->read_cb = s4_canread;
1366 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1368 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1369 } else {
1370 connect_data->query_data = purple_dnsquery_a(connect_data->host,
1371 connect_data->port, s4_host_resolved, connect_data);
1373 if (connect_data->query_data == NULL) {
1374 purple_debug_error("proxy", "dns query failed unexpectedly.\n");
1375 purple_proxy_connect_data_destroy(connect_data);
1380 static void
1381 proxy_connect_socks4(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
1383 int flags;
1385 purple_debug_info("proxy",
1386 "Connecting to %s:%d via %s:%d using SOCKS4\n",
1387 connect_data->host, connect_data->port,
1388 purple_proxy_info_get_host(connect_data->gpi),
1389 purple_proxy_info_get_port(connect_data->gpi));
1391 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
1392 if (connect_data->fd < 0)
1394 purple_proxy_connect_data_disconnect_formatted(connect_data,
1395 _("Unable to create socket: %s"), g_strerror(errno));
1396 return;
1399 flags = fcntl(connect_data->fd, F_GETFL);
1400 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
1401 #ifndef _WIN32
1402 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
1403 #endif
1405 if (connect(connect_data->fd, addr, addrlen) != 0)
1407 if ((errno == EINPROGRESS) || (errno == EINTR))
1409 purple_debug_info("proxy", "Connection in progress.\n");
1410 connect_data->inpa = purple_input_add(connect_data->fd,
1411 PURPLE_INPUT_WRITE, s4_canwrite, connect_data);
1413 else
1415 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
1418 else
1420 purple_debug_info("proxy", "Connected immediately.\n");
1422 s4_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1426 static gboolean
1427 s5_ensure_buffer_length(PurpleProxyConnectData *connect_data, int len)
1429 if(connect_data->read_len < len) {
1430 if(connect_data->read_buf_len < len) {
1431 /* it's not just that we haven't read enough, it's that we haven't tried to read enough yet */
1432 purple_debug_info("s5", "reallocing from %" G_GSIZE_FORMAT
1433 " to %d\n", connect_data->read_buf_len, len);
1434 connect_data->read_buf_len = len;
1435 connect_data->read_buffer = g_realloc(connect_data->read_buffer, connect_data->read_buf_len);
1437 return FALSE;
1440 return TRUE;
1443 static void
1444 s5_canread_again(gpointer data, gint source, PurpleInputCondition cond)
1446 guchar *dest, *buf;
1447 PurpleProxyConnectData *connect_data = data;
1448 int len;
1450 if (connect_data->read_buffer == NULL) {
1451 connect_data->read_buf_len = 5;
1452 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1453 connect_data->read_len = 0;
1456 dest = connect_data->read_buffer + connect_data->read_len;
1457 buf = connect_data->read_buffer;
1459 len = read(connect_data->fd, dest, (connect_data->read_buf_len - connect_data->read_len));
1461 if (len == 0)
1463 purple_proxy_connect_data_disconnect(connect_data,
1464 _("Server closed the connection"));
1465 return;
1468 if (len < 0)
1470 if (errno == EAGAIN)
1471 /* No worries */
1472 return;
1474 /* Error! */
1475 purple_proxy_connect_data_disconnect_formatted(connect_data,
1476 _("Lost connection with server: %s"), g_strerror(errno));
1477 return;
1480 connect_data->read_len += len;
1482 if(connect_data->read_len < 4)
1483 return;
1485 if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
1486 if ((buf[0] == 0x05) && (buf[1] < 0x09)) {
1487 purple_debug_error("socks5 proxy", "%s", socks5errors[buf[1]]);
1488 purple_proxy_connect_data_disconnect(connect_data,
1489 socks5errors[buf[1]]);
1490 } else {
1491 purple_debug_error("socks5 proxy", "Bad data.\n");
1492 purple_proxy_connect_data_disconnect(connect_data,
1493 _("Received invalid data on connection with server"));
1495 return;
1498 /* Skip past BND.ADDR */
1499 switch(buf[3]) {
1500 case 0x01: /* the address is a version-4 IP address, with a length of 4 octets */
1501 if(!s5_ensure_buffer_length(connect_data, 4 + 4))
1502 return;
1503 buf += 4 + 4;
1504 break;
1505 case 0x03: /* the address field contains a fully-qualified domain name. The first
1506 octet of the address field contains the number of octets of name that
1507 follow, there is no terminating NUL octet. */
1508 if(!s5_ensure_buffer_length(connect_data, 4 + 1))
1509 return;
1510 buf += 4;
1511 if(!s5_ensure_buffer_length(connect_data, 4 + 1 + buf[0]))
1512 return;
1513 buf += buf[0] + 1;
1514 break;
1515 case 0x04: /* the address is a version-6 IP address, with a length of 16 octets */
1516 if(!s5_ensure_buffer_length(connect_data, 4 + 16))
1517 return;
1518 buf += 4 + 16;
1519 break;
1520 default:
1521 purple_debug_error("socks5 proxy", "Invalid ATYP received (0x%X)\n", buf[3]);
1522 purple_proxy_connect_data_disconnect(connect_data,
1523 _("Received invalid data on connection with server"));
1524 return;
1527 /* Skip past BND.PORT */
1528 if(!s5_ensure_buffer_length(connect_data, (buf - connect_data->read_buffer) + 2))
1529 return;
1531 purple_proxy_connect_data_connected(connect_data);
1534 static void
1535 s5_sendconnect(gpointer data, int source)
1537 PurpleProxyConnectData *connect_data = data;
1538 size_t hlen = strlen(connect_data->host);
1539 connect_data->write_buf_len = 5 + hlen + 2;
1540 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1541 connect_data->written_len = 0;
1543 connect_data->write_buffer[0] = 0x05;
1544 connect_data->write_buffer[1] = 0x01; /* CONNECT */
1545 connect_data->write_buffer[2] = 0x00; /* reserved */
1546 connect_data->write_buffer[3] = 0x03; /* address type -- host name */
1547 connect_data->write_buffer[4] = hlen;
1548 memcpy(connect_data->write_buffer + 5, connect_data->host, hlen);
1549 connect_data->write_buffer[5 + hlen] = connect_data->port >> 8;
1550 connect_data->write_buffer[5 + hlen + 1] = connect_data->port & 0xff;
1552 connect_data->read_cb = s5_canread_again;
1554 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1555 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1558 static void
1559 s5_readauth(gpointer data, gint source, PurpleInputCondition cond)
1561 PurpleProxyConnectData *connect_data = data;
1562 int len;
1564 if (connect_data->read_buffer == NULL) {
1565 connect_data->read_buf_len = 2;
1566 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1567 connect_data->read_len = 0;
1570 purple_debug_info("socks5 proxy", "Got auth response.\n");
1572 len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
1573 connect_data->read_buf_len - connect_data->read_len);
1575 if (len == 0)
1577 purple_proxy_connect_data_disconnect(connect_data,
1578 _("Server closed the connection"));
1579 return;
1582 if (len < 0)
1584 if (errno == EAGAIN)
1585 /* No worries */
1586 return;
1588 /* Error! */
1589 purple_proxy_connect_data_disconnect_formatted(connect_data,
1590 _("Lost connection with server: %s"), g_strerror(errno));
1591 return;
1594 connect_data->read_len += len;
1595 if (connect_data->read_len < 2)
1596 return;
1598 purple_input_remove(connect_data->inpa);
1599 connect_data->inpa = 0;
1601 if ((connect_data->read_buffer[0] != 0x01) || (connect_data->read_buffer[1] != 0x00)) {
1602 purple_proxy_connect_data_disconnect(connect_data,
1603 _("Received invalid data on connection with server"));
1604 return;
1607 g_free(connect_data->read_buffer);
1608 connect_data->read_buffer = NULL;
1610 s5_sendconnect(connect_data, connect_data->fd);
1613 static void
1614 hmacmd5_chap(const unsigned char * challenge, int challen, const char * passwd, unsigned char * response)
1616 PurpleCipher *cipher;
1617 PurpleCipherContext *ctx;
1618 int i;
1619 unsigned char Kxoripad[65];
1620 unsigned char Kxoropad[65];
1621 size_t pwlen;
1623 cipher = purple_ciphers_find_cipher("md5");
1624 ctx = purple_cipher_context_new(cipher, NULL);
1626 memset(Kxoripad,0,sizeof(Kxoripad));
1627 memset(Kxoropad,0,sizeof(Kxoropad));
1629 pwlen=strlen(passwd);
1630 if (pwlen>64) {
1631 purple_cipher_context_append(ctx, (const guchar *)passwd, strlen(passwd));
1632 purple_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL);
1633 pwlen=16;
1634 } else {
1635 memcpy(Kxoripad, passwd, pwlen);
1637 memcpy(Kxoropad,Kxoripad,pwlen);
1639 for (i=0;i<64;i++) {
1640 Kxoripad[i]^=0x36;
1641 Kxoropad[i]^=0x5c;
1644 purple_cipher_context_reset(ctx, NULL);
1645 purple_cipher_context_append(ctx, Kxoripad, 64);
1646 purple_cipher_context_append(ctx, challenge, challen);
1647 purple_cipher_context_digest(ctx, sizeof(Kxoripad), Kxoripad, NULL);
1649 purple_cipher_context_reset(ctx, NULL);
1650 purple_cipher_context_append(ctx, Kxoropad, 64);
1651 purple_cipher_context_append(ctx, Kxoripad, 16);
1652 purple_cipher_context_digest(ctx, 16, response, NULL);
1654 purple_cipher_context_destroy(ctx);
1657 static void
1658 s5_readchap(gpointer data, gint source, PurpleInputCondition cond);
1661 * Return how many bytes we processed
1662 * -1 means we've shouldn't keep reading from the buffer
1664 static gssize
1665 s5_parse_chap_msg(PurpleProxyConnectData *connect_data)
1667 guchar *buf, *cmdbuf = connect_data->read_buffer;
1668 int len, navas, currentav;
1670 purple_debug_misc("socks5 proxy", "Reading CHAP message: %x\n", *cmdbuf);
1672 if (*cmdbuf != 0x01) {
1673 purple_proxy_connect_data_disconnect(connect_data,
1674 _("Received invalid data on connection with server"));
1675 return -1;
1677 cmdbuf++;
1679 navas = *cmdbuf;
1681 purple_debug_misc("socks5 proxy", "Expecting %d attribute(s).\n", navas);
1683 cmdbuf++;
1685 for (currentav = 0; currentav < navas; currentav++) {
1687 len = connect_data->read_len - (cmdbuf - connect_data->read_buffer);
1688 /* We don't have enough data to even know how long the next attribute is,
1689 * or we don't have the full length of the next attribute. */
1690 if (len < 2 || len < (cmdbuf[1] + 2)) {
1691 /* Clear out the attributes that have been read - decrease the attribute count */
1692 connect_data->read_buffer[1] = navas - currentav;
1693 /* Move the unprocessed data into the first attribute position */
1694 memmove((connect_data->read_buffer + 2), cmdbuf, len);
1695 /* Decrease the read count accordingly */
1696 connect_data->read_len = len + 2;
1698 purple_debug_info("socks5 proxy", "Need more data to retrieve attribute %d.\n", currentav);
1700 return -1;
1703 buf = cmdbuf + 2;
1705 if (cmdbuf[1] == 0) {
1706 purple_debug_error("socks5 proxy", "Attribute %x Value length of 0; ignoring.\n", cmdbuf[0]);
1707 cmdbuf = buf;
1708 continue;
1711 switch (cmdbuf[0]) {
1712 case 0x00:
1713 purple_debug_info("socks5 proxy", "Received STATUS of %x\n", buf[0]);
1714 /* Did auth work? */
1715 if (buf[0] == 0x00) {
1716 purple_input_remove(connect_data->inpa);
1717 connect_data->inpa = 0;
1718 g_free(connect_data->read_buffer);
1719 connect_data->read_buffer = NULL;
1720 /* Success */
1721 s5_sendconnect(connect_data, connect_data->fd);
1722 } else {
1723 /* Failure */
1724 purple_debug_warning("proxy",
1725 "socks5 CHAP authentication "
1726 "failed. Disconnecting...");
1727 purple_proxy_connect_data_disconnect(connect_data,
1728 _("Authentication failed"));
1730 return -1;
1731 case 0x01:
1732 /* We've already validated that cmdbuf[1] is sane. */
1733 purple_debug_info("socks5 proxy", "Received TEXT-MESSAGE of '%.*s'\n", (int) cmdbuf[1], buf);
1734 break;
1735 case 0x03:
1736 purple_debug_info("socks5 proxy", "Received CHALLENGE\n");
1737 /* Server wants our credentials */
1739 connect_data->write_buf_len = 16 + 4;
1740 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1741 connect_data->written_len = 0;
1743 hmacmd5_chap(buf, cmdbuf[1],
1744 purple_proxy_info_get_password(connect_data->gpi),
1745 connect_data->write_buffer + 4);
1746 /* TODO: What about USER-IDENTITY? */
1747 connect_data->write_buffer[0] = 0x01;
1748 connect_data->write_buffer[1] = 0x01;
1749 connect_data->write_buffer[2] = 0x04;
1750 connect_data->write_buffer[3] = 0x10;
1752 purple_input_remove(connect_data->inpa);
1753 g_free(connect_data->read_buffer);
1754 connect_data->read_buffer = NULL;
1756 connect_data->read_cb = s5_readchap;
1758 connect_data->inpa = purple_input_add(connect_data->fd,
1759 PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
1761 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1762 return -1;
1763 case 0x11:
1764 purple_debug_info("socks5 proxy", "Received ALGORIGTHMS of %x\n", buf[0]);
1765 /* Server wants to select an algorithm */
1766 if (buf[0] != 0x85) {
1767 /* Only currently support HMAC-MD5 */
1768 purple_debug_warning("proxy",
1769 "Server tried to select an "
1770 "algorithm that we did not advertise "
1771 "as supporting. This is a violation "
1772 "of the socks5 CHAP specification. "
1773 "Disconnecting...");
1774 purple_proxy_connect_data_disconnect(connect_data,
1775 _("Received invalid data on connection with server"));
1776 return -1;
1778 break;
1779 default:
1780 purple_debug_info("socks5 proxy", "Received unused command %x, length=%d\n", cmdbuf[0], cmdbuf[1]);
1782 cmdbuf = buf + cmdbuf[1];
1785 return (cmdbuf - connect_data->read_buffer);
1788 static void
1789 s5_readchap(gpointer data, gint source, PurpleInputCondition cond)
1791 gssize msg_ret;
1792 PurpleProxyConnectData *connect_data = data;
1793 int len;
1795 purple_debug(PURPLE_DEBUG_INFO, "socks5 proxy", "Got CHAP response.\n");
1797 if (connect_data->read_buffer == NULL) {
1798 /* A big enough butfer to read the message header (2 bytes) and at least one complete attribute and value (1 + 1 + 255). */
1799 connect_data->read_buf_len = 259;
1800 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1801 connect_data->read_len = 0;
1804 if (connect_data->read_buf_len - connect_data->read_len == 0) {
1805 /*If the stuff below is right, this shouldn't be possible. */
1806 purple_debug_error("socks5 proxy", "This is about to suck because the read buffer is full (shouldn't happen).\n");
1809 len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
1810 connect_data->read_buf_len - connect_data->read_len);
1812 if (len == 0) {
1813 purple_proxy_connect_data_disconnect(connect_data,
1814 _("Server closed the connection"));
1815 return;
1818 if (len < 0) {
1819 if (errno == EAGAIN)
1820 /* No worries */
1821 return;
1823 /* Error! */
1824 purple_proxy_connect_data_disconnect_formatted(connect_data,
1825 _("Lost connection with server: %s"), g_strerror(errno));
1826 return;
1829 connect_data->read_len += len;
1831 /* We may have read more than one message into the buffer, we need to make sure to process them all */
1832 while (1) {
1834 /* We need more to be able to read this message */
1835 if (connect_data->read_len < 2)
1836 return;
1838 msg_ret = s5_parse_chap_msg(connect_data);
1840 if (msg_ret < 0)
1841 return;
1843 /* See if we have another message already in the buffer */
1844 if ((len = connect_data->read_len - msg_ret) > 0) {
1846 /* Move on to the next message */
1847 memmove(connect_data->read_buffer, connect_data->read_buffer + msg_ret, len);
1848 /* Decrease the read count accordingly */
1849 connect_data->read_len = len;
1851 /* Try to read the message that connect_data->read_buffer now points to */
1852 continue;
1855 break;
1858 /* Fell through. We ran out of CHAP events to process, but haven't
1859 * succeeded or failed authentication - there may be more to come.
1860 * If this is the case, come straight back here. */
1862 purple_debug_info("socks5 proxy", "Waiting for another message from which to read CHAP info.\n");
1864 /* We've processed all the available attributes, so get ready for a whole new message */
1865 g_free(connect_data->read_buffer);
1866 connect_data->read_buffer = NULL;
1869 static void
1870 s5_canread(gpointer data, gint source, PurpleInputCondition cond)
1872 PurpleProxyConnectData *connect_data = data;
1873 int len;
1875 if (connect_data->read_buffer == NULL) {
1876 connect_data->read_buf_len = 2;
1877 connect_data->read_buffer = g_malloc(connect_data->read_buf_len);
1878 connect_data->read_len = 0;
1881 purple_debug_info("socks5 proxy", "Able to read.\n");
1883 len = read(connect_data->fd, connect_data->read_buffer + connect_data->read_len,
1884 connect_data->read_buf_len - connect_data->read_len);
1886 if (len == 0)
1888 purple_proxy_connect_data_disconnect(connect_data,
1889 _("Server closed the connection"));
1890 return;
1893 if (len < 0)
1895 if (errno == EAGAIN)
1896 /* No worries */
1897 return;
1899 /* Error! */
1900 purple_proxy_connect_data_disconnect_formatted(connect_data,
1901 _("Lost connection with server: %s"), g_strerror(errno));
1902 return;
1905 connect_data->read_len += len;
1906 if (connect_data->read_len < 2)
1907 return;
1909 purple_input_remove(connect_data->inpa);
1910 connect_data->inpa = 0;
1912 if ((connect_data->read_buffer[0] != 0x05) || (connect_data->read_buffer[1] == 0xff)) {
1913 purple_proxy_connect_data_disconnect(connect_data,
1914 _("Received invalid data on connection with server"));
1915 return;
1918 if (connect_data->read_buffer[1] == 0x02) {
1919 size_t i, j;
1920 const char *u, *p;
1922 u = purple_proxy_info_get_username(connect_data->gpi);
1923 p = purple_proxy_info_get_password(connect_data->gpi);
1925 i = (u == NULL) ? 0 : strlen(u);
1926 j = (p == NULL) ? 0 : strlen(p);
1928 connect_data->write_buf_len = 1 + 1 + i + 1 + j;
1929 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1930 connect_data->written_len = 0;
1932 connect_data->write_buffer[0] = 0x01; /* version 1 */
1933 connect_data->write_buffer[1] = i;
1934 if (u != NULL)
1935 memcpy(connect_data->write_buffer + 2, u, i);
1936 connect_data->write_buffer[2 + i] = j;
1937 if (p != NULL)
1938 memcpy(connect_data->write_buffer + 2 + i + 1, p, j);
1940 g_free(connect_data->read_buffer);
1941 connect_data->read_buffer = NULL;
1943 connect_data->read_cb = s5_readauth;
1945 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE,
1946 proxy_do_write, connect_data);
1948 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1950 return;
1951 } else if (connect_data->read_buffer[1] == 0x03) {
1952 size_t userlen;
1953 userlen = strlen(purple_proxy_info_get_username(connect_data->gpi));
1955 connect_data->write_buf_len = 7 + userlen;
1956 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
1957 connect_data->written_len = 0;
1959 connect_data->write_buffer[0] = 0x01;
1960 connect_data->write_buffer[1] = 0x02;
1961 connect_data->write_buffer[2] = 0x11;
1962 connect_data->write_buffer[3] = 0x01;
1963 connect_data->write_buffer[4] = 0x85;
1964 connect_data->write_buffer[5] = 0x02;
1965 connect_data->write_buffer[6] = userlen;
1966 memcpy(connect_data->write_buffer + 7,
1967 purple_proxy_info_get_username(connect_data->gpi), userlen);
1969 g_free(connect_data->read_buffer);
1970 connect_data->read_buffer = NULL;
1972 connect_data->read_cb = s5_readchap;
1974 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE,
1975 proxy_do_write, connect_data);
1977 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
1979 return;
1980 } else {
1981 g_free(connect_data->read_buffer);
1982 connect_data->read_buffer = NULL;
1984 s5_sendconnect(connect_data, connect_data->fd);
1988 static void
1989 s5_canwrite(gpointer data, gint source, PurpleInputCondition cond)
1991 unsigned char buf[5];
1992 int i;
1993 PurpleProxyConnectData *connect_data = data;
1994 int error = ETIMEDOUT;
1995 int ret;
1997 purple_debug_info("socks5 proxy", "Connected.\n");
1999 if (connect_data->inpa > 0)
2001 purple_input_remove(connect_data->inpa);
2002 connect_data->inpa = 0;
2005 ret = purple_input_get_error(connect_data->fd, &error);
2006 if ((ret != 0) || (error != 0))
2008 if (ret != 0)
2009 error = errno;
2010 purple_proxy_connect_data_disconnect(connect_data, g_strerror(error));
2011 return;
2014 i = 0;
2015 buf[0] = 0x05; /* SOCKS version 5 */
2017 if (purple_proxy_info_get_username(connect_data->gpi) != NULL) {
2018 buf[1] = 0x03; /* three methods */
2019 buf[2] = 0x00; /* no authentication */
2020 buf[3] = 0x03; /* CHAP authentication */
2021 buf[4] = 0x02; /* username/password authentication */
2022 i = 5;
2024 else {
2025 buf[1] = 0x01;
2026 buf[2] = 0x00;
2027 i = 3;
2030 connect_data->write_buf_len = i;
2031 connect_data->write_buffer = g_malloc(connect_data->write_buf_len);
2032 memcpy(connect_data->write_buffer, buf, i);
2034 connect_data->read_cb = s5_canread;
2036 connect_data->inpa = purple_input_add(connect_data->fd, PURPLE_INPUT_WRITE, proxy_do_write, connect_data);
2037 proxy_do_write(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
2040 static void
2041 proxy_connect_socks5(PurpleProxyConnectData *connect_data, struct sockaddr *addr, socklen_t addrlen)
2043 int flags;
2045 purple_debug_info("proxy",
2046 "Connecting to %s:%d via %s:%d using SOCKS5\n",
2047 connect_data->host, connect_data->port,
2048 purple_proxy_info_get_host(connect_data->gpi),
2049 purple_proxy_info_get_port(connect_data->gpi));
2051 connect_data->fd = socket(addr->sa_family, SOCK_STREAM, 0);
2052 if (connect_data->fd < 0)
2054 purple_proxy_connect_data_disconnect_formatted(connect_data,
2055 _("Unable to create socket: %s"), g_strerror(errno));
2056 return;
2059 flags = fcntl(connect_data->fd, F_GETFL);
2060 fcntl(connect_data->fd, F_SETFL, flags | O_NONBLOCK);
2061 #ifndef _WIN32
2062 fcntl(connect_data->fd, F_SETFD, FD_CLOEXEC);
2063 #endif
2065 if (connect(connect_data->fd, addr, addrlen) != 0)
2067 if ((errno == EINPROGRESS) || (errno == EINTR))
2069 purple_debug_info("socks5 proxy", "Connection in progress\n");
2070 connect_data->inpa = purple_input_add(connect_data->fd,
2071 PURPLE_INPUT_WRITE, s5_canwrite, connect_data);
2073 else
2075 purple_proxy_connect_data_disconnect(connect_data, g_strerror(errno));
2078 else
2080 purple_debug_info("proxy", "Connected immediately.\n");
2082 s5_canwrite(connect_data, connect_data->fd, PURPLE_INPUT_WRITE);
2087 * This function attempts to connect to the next IP address in the list
2088 * of IP addresses returned to us by purple_dnsquery_a() and attemps
2089 * to connect to each one. This is called after the hostname is
2090 * resolved, and each time a connection attempt fails (assuming there
2091 * is another IP address to try).
2093 #ifndef INET6_ADDRSTRLEN
2094 #define INET6_ADDRSTRLEN 46
2095 #endif
2097 static void try_connect(PurpleProxyConnectData *connect_data)
2099 socklen_t addrlen;
2100 struct sockaddr *addr;
2101 char ipaddr[INET6_ADDRSTRLEN];
2103 addrlen = GPOINTER_TO_INT(connect_data->hosts->data);
2104 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
2105 addr = connect_data->hosts->data;
2106 connect_data->hosts = g_slist_remove(connect_data->hosts, connect_data->hosts->data);
2107 #ifdef HAVE_INET_NTOP
2108 if (addr->sa_family == AF_INET)
2109 inet_ntop(addr->sa_family, &((struct sockaddr_in *)addr)->sin_addr,
2110 ipaddr, sizeof(ipaddr));
2111 else if (addr->sa_family == AF_INET6)
2112 inet_ntop(addr->sa_family, &((struct sockaddr_in6 *)addr)->sin6_addr,
2113 ipaddr, sizeof(ipaddr));
2114 #else
2115 memcpy(ipaddr, inet_ntoa(((struct sockaddr_in *)addr)->sin_addr),
2116 sizeof(ipaddr));
2117 #endif
2118 purple_debug_info("proxy", "Attempting connection to %s\n", ipaddr);
2120 if (connect_data->socket_type == SOCK_DGRAM) {
2121 proxy_connect_udp_none(connect_data, addr, addrlen);
2122 g_free(addr);
2123 return;
2126 switch (purple_proxy_info_get_type(connect_data->gpi)) {
2127 case PURPLE_PROXY_NONE:
2128 proxy_connect_none(connect_data, addr, addrlen);
2129 break;
2131 case PURPLE_PROXY_HTTP:
2132 proxy_connect_http(connect_data, addr, addrlen);
2133 break;
2135 case PURPLE_PROXY_SOCKS4:
2136 proxy_connect_socks4(connect_data, addr, addrlen);
2137 break;
2139 case PURPLE_PROXY_SOCKS5:
2140 proxy_connect_socks5(connect_data, addr, addrlen);
2141 break;
2143 case PURPLE_PROXY_USE_ENVVAR:
2144 proxy_connect_http(connect_data, addr, addrlen);
2145 break;
2147 default:
2148 break;
2151 g_free(addr);
2154 static void
2155 connection_host_resolved(GSList *hosts, gpointer data,
2156 const char *error_message)
2158 PurpleProxyConnectData *connect_data;
2160 connect_data = data;
2161 connect_data->query_data = NULL;
2163 if (error_message != NULL)
2165 purple_proxy_connect_data_disconnect(connect_data, error_message);
2166 return;
2169 if (hosts == NULL)
2171 purple_proxy_connect_data_disconnect(connect_data, _("Unable to resolve hostname"));
2172 return;
2175 connect_data->hosts = hosts;
2177 try_connect(connect_data);
2180 PurpleProxyInfo *
2181 purple_proxy_get_setup(PurpleAccount *account)
2183 PurpleProxyInfo *gpi = NULL;
2184 const gchar *tmp;
2186 /* This is used as a fallback so we don't overwrite the selected proxy type */
2187 static PurpleProxyInfo *tmp_none_proxy_info = NULL;
2188 if (!tmp_none_proxy_info) {
2189 tmp_none_proxy_info = purple_proxy_info_new();
2190 purple_proxy_info_set_type(tmp_none_proxy_info, PURPLE_PROXY_NONE);
2193 if (account && purple_account_get_proxy_info(account) != NULL) {
2194 gpi = purple_account_get_proxy_info(account);
2195 if (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_GLOBAL)
2196 gpi = NULL;
2198 if (gpi == NULL) {
2199 if (purple_running_gnome())
2200 gpi = purple_gnome_proxy_get_info();
2201 else
2202 gpi = purple_global_proxy_get_info();
2205 if (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR) {
2206 if ((tmp = g_getenv("HTTP_PROXY")) != NULL ||
2207 (tmp = g_getenv("http_proxy")) != NULL ||
2208 (tmp = g_getenv("HTTPPROXY")) != NULL) {
2209 char *proxyhost, *proxyuser, *proxypasswd;
2210 int proxyport;
2212 /* http_proxy-format:
2213 * export http_proxy="http://user:passwd@your.proxy.server:port/"
2215 if(purple_url_parse(tmp, &proxyhost, &proxyport, NULL, &proxyuser, &proxypasswd)) {
2216 purple_proxy_info_set_host(gpi, proxyhost);
2217 g_free(proxyhost);
2219 purple_proxy_info_set_username(gpi, proxyuser);
2220 g_free(proxyuser);
2222 purple_proxy_info_set_password(gpi, proxypasswd);
2223 g_free(proxypasswd);
2225 /* only for backward compatibility */
2226 if (proxyport == 80 &&
2227 ((tmp = g_getenv("HTTP_PROXY_PORT")) != NULL ||
2228 (tmp = g_getenv("http_proxy_port")) != NULL ||
2229 (tmp = g_getenv("HTTPPROXYPORT")) != NULL))
2230 proxyport = atoi(tmp);
2232 purple_proxy_info_set_port(gpi, proxyport);
2234 /* XXX: Do we want to skip this step if user/password were part of url? */
2235 if ((tmp = g_getenv("HTTP_PROXY_USER")) != NULL ||
2236 (tmp = g_getenv("http_proxy_user")) != NULL ||
2237 (tmp = g_getenv("HTTPPROXYUSER")) != NULL)
2238 purple_proxy_info_set_username(gpi, tmp);
2240 if ((tmp = g_getenv("HTTP_PROXY_PASS")) != NULL ||
2241 (tmp = g_getenv("http_proxy_pass")) != NULL ||
2242 (tmp = g_getenv("HTTPPROXYPASS")) != NULL)
2243 purple_proxy_info_set_password(gpi, tmp);
2246 } else {
2247 #ifdef _WIN32
2248 PurpleProxyInfo *wgpi;
2249 if ((wgpi = purple_win32_proxy_get_info()) != NULL)
2250 return wgpi;
2251 #endif
2252 /* no proxy environment variable found, don't use a proxy */
2253 purple_debug_info("proxy", "No environment settings found, not using a proxy\n");
2254 gpi = tmp_none_proxy_info;
2259 return gpi;
2262 PurpleProxyConnectData *
2263 purple_proxy_connect(void *handle, PurpleAccount *account,
2264 const char *host, int port,
2265 PurpleProxyConnectFunction connect_cb, gpointer data)
2267 const char *connecthost = host;
2268 int connectport = port;
2269 PurpleProxyConnectData *connect_data;
2271 g_return_val_if_fail(host != NULL, NULL);
2272 g_return_val_if_fail(port > 0, NULL);
2273 g_return_val_if_fail(connect_cb != NULL, NULL);
2275 connect_data = g_new0(PurpleProxyConnectData, 1);
2276 connect_data->fd = -1;
2277 connect_data->socket_type = SOCK_STREAM;
2278 connect_data->handle = handle;
2279 connect_data->connect_cb = connect_cb;
2280 connect_data->data = data;
2281 connect_data->host = g_strdup(host);
2282 connect_data->port = port;
2283 connect_data->gpi = purple_proxy_get_setup(account);
2285 if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) &&
2286 (purple_proxy_info_get_host(connect_data->gpi) == NULL ||
2287 purple_proxy_info_get_port(connect_data->gpi) <= 0)) {
2289 purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid."));
2290 purple_proxy_connect_data_destroy(connect_data);
2291 return NULL;
2294 switch (purple_proxy_info_get_type(connect_data->gpi))
2296 case PURPLE_PROXY_NONE:
2297 break;
2299 case PURPLE_PROXY_HTTP:
2300 case PURPLE_PROXY_SOCKS4:
2301 case PURPLE_PROXY_SOCKS5:
2302 case PURPLE_PROXY_USE_ENVVAR:
2303 connecthost = purple_proxy_info_get_host(connect_data->gpi);
2304 connectport = purple_proxy_info_get_port(connect_data->gpi);
2305 break;
2307 default:
2308 purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n",
2309 purple_proxy_info_get_type(connect_data->gpi));
2310 purple_proxy_connect_data_destroy(connect_data);
2311 return NULL;
2314 connect_data->query_data = purple_dnsquery_a(connecthost,
2315 connectport, connection_host_resolved, connect_data);
2316 if (connect_data->query_data == NULL)
2318 purple_debug_error("proxy", "dns query failed unexpectedly.\n");
2319 purple_proxy_connect_data_destroy(connect_data);
2320 return NULL;
2323 handles = g_slist_prepend(handles, connect_data);
2325 return connect_data;
2328 PurpleProxyConnectData *
2329 purple_proxy_connect_udp(void *handle, PurpleAccount *account,
2330 const char *host, int port,
2331 PurpleProxyConnectFunction connect_cb, gpointer data)
2333 const char *connecthost = host;
2334 int connectport = port;
2335 PurpleProxyConnectData *connect_data;
2337 g_return_val_if_fail(host != NULL, NULL);
2338 g_return_val_if_fail(port > 0, NULL);
2339 g_return_val_if_fail(connect_cb != NULL, NULL);
2341 connect_data = g_new0(PurpleProxyConnectData, 1);
2342 connect_data->fd = -1;
2343 connect_data->socket_type = SOCK_DGRAM;
2344 connect_data->handle = handle;
2345 connect_data->connect_cb = connect_cb;
2346 connect_data->data = data;
2347 connect_data->host = g_strdup(host);
2348 connect_data->port = port;
2349 connect_data->gpi = purple_proxy_get_setup(account);
2351 if ((purple_proxy_info_get_type(connect_data->gpi) != PURPLE_PROXY_NONE) &&
2352 (purple_proxy_info_get_host(connect_data->gpi) == NULL ||
2353 purple_proxy_info_get_port(connect_data->gpi) <= 0)) {
2355 purple_notify_error(NULL, NULL, _("Invalid proxy settings"), _("Either the host name or port number specified for your given proxy type is invalid."));
2356 purple_proxy_connect_data_destroy(connect_data);
2357 return NULL;
2360 switch (purple_proxy_info_get_type(connect_data->gpi))
2362 case PURPLE_PROXY_NONE:
2363 break;
2365 case PURPLE_PROXY_HTTP:
2366 case PURPLE_PROXY_SOCKS4:
2367 case PURPLE_PROXY_SOCKS5:
2368 case PURPLE_PROXY_USE_ENVVAR:
2369 purple_debug_info("proxy", "Ignoring Proxy type (%d) for UDP.\n",
2370 purple_proxy_info_get_type(connect_data->gpi));
2371 break;
2373 default:
2374 purple_debug_error("proxy", "Invalid Proxy type (%d) specified.\n",
2375 purple_proxy_info_get_type(connect_data->gpi));
2376 purple_proxy_connect_data_destroy(connect_data);
2377 return NULL;
2380 connect_data->query_data = purple_dnsquery_a(connecthost,
2381 connectport, connection_host_resolved, connect_data);
2382 if (connect_data->query_data == NULL)
2384 purple_proxy_connect_data_destroy(connect_data);
2385 return NULL;
2388 handles = g_slist_prepend(handles, connect_data);
2390 return connect_data;
2394 * Combine some of this code with purple_proxy_connect()
2396 PurpleProxyConnectData *
2397 purple_proxy_connect_socks5(void *handle, PurpleProxyInfo *gpi,
2398 const char *host, int port,
2399 PurpleProxyConnectFunction connect_cb,
2400 gpointer data)
2402 PurpleProxyConnectData *connect_data;
2404 g_return_val_if_fail(host != NULL, NULL);
2405 g_return_val_if_fail(port >= 0, NULL);
2406 g_return_val_if_fail(connect_cb != NULL, NULL);
2408 connect_data = g_new0(PurpleProxyConnectData, 1);
2409 connect_data->fd = -1;
2410 connect_data->socket_type = SOCK_STREAM;
2411 connect_data->handle = handle;
2412 connect_data->connect_cb = connect_cb;
2413 connect_data->data = data;
2414 connect_data->host = g_strdup(host);
2415 connect_data->port = port;
2416 connect_data->gpi = gpi;
2418 connect_data->query_data =
2419 purple_dnsquery_a(purple_proxy_info_get_host(gpi),
2420 purple_proxy_info_get_port(gpi),
2421 connection_host_resolved, connect_data);
2422 if (connect_data->query_data == NULL)
2424 purple_proxy_connect_data_destroy(connect_data);
2425 return NULL;
2428 handles = g_slist_prepend(handles, connect_data);
2430 return connect_data;
2433 void
2434 purple_proxy_connect_cancel(PurpleProxyConnectData *connect_data)
2436 purple_proxy_connect_data_disconnect(connect_data, NULL);
2437 purple_proxy_connect_data_destroy(connect_data);
2440 void
2441 purple_proxy_connect_cancel_with_handle(void *handle)
2443 GSList *l, *l_next;
2445 for (l = handles; l != NULL; l = l_next) {
2446 PurpleProxyConnectData *connect_data = l->data;
2448 l_next = l->next;
2450 if (connect_data->handle == handle)
2451 purple_proxy_connect_cancel(connect_data);
2455 static void
2456 proxy_pref_cb(const char *name, PurplePrefType type,
2457 gconstpointer value, gpointer data)
2459 PurpleProxyInfo *info = purple_global_proxy_get_info();
2461 if (purple_strequal(name, "/purple/proxy/type")) {
2462 int proxytype;
2463 const char *type = value;
2465 if (purple_strequal(type, "none"))
2466 proxytype = PURPLE_PROXY_NONE;
2467 else if (purple_strequal(type, "http"))
2468 proxytype = PURPLE_PROXY_HTTP;
2469 else if (purple_strequal(type, "socks4"))
2470 proxytype = PURPLE_PROXY_SOCKS4;
2471 else if (purple_strequal(type, "socks5"))
2472 proxytype = PURPLE_PROXY_SOCKS5;
2473 else if (purple_strequal(type, "envvar"))
2474 proxytype = PURPLE_PROXY_USE_ENVVAR;
2475 else
2476 proxytype = -1;
2478 purple_proxy_info_set_type(info, proxytype);
2479 } else if (purple_strequal(name, "/purple/proxy/host"))
2480 purple_proxy_info_set_host(info, value);
2481 else if (purple_strequal(name, "/purple/proxy/port"))
2482 purple_proxy_info_set_port(info, GPOINTER_TO_INT(value));
2483 else if (purple_strequal(name, "/purple/proxy/username"))
2484 purple_proxy_info_set_username(info, value);
2485 else if (purple_strequal(name, "/purple/proxy/password"))
2486 purple_proxy_info_set_password(info, value);
2489 void *
2490 purple_proxy_get_handle()
2492 static int handle;
2494 return &handle;
2497 void
2498 purple_proxy_init(void)
2500 void *handle;
2502 /* Initialize a default proxy info struct. */
2503 global_proxy_info = purple_proxy_info_new();
2505 /* Proxy */
2506 purple_prefs_add_none("/purple/proxy");
2507 purple_prefs_add_string("/purple/proxy/type", "none");
2508 purple_prefs_add_string("/purple/proxy/host", "");
2509 purple_prefs_add_int("/purple/proxy/port", 0);
2510 purple_prefs_add_string("/purple/proxy/username", "");
2511 purple_prefs_add_string("/purple/proxy/password", "");
2512 purple_prefs_add_bool("/purple/proxy/socks4_remotedns", FALSE);
2514 /* Setup callbacks for the preferences. */
2515 handle = purple_proxy_get_handle();
2516 purple_prefs_connect_callback(handle, "/purple/proxy/type", proxy_pref_cb,
2517 NULL);
2518 purple_prefs_connect_callback(handle, "/purple/proxy/host", proxy_pref_cb,
2519 NULL);
2520 purple_prefs_connect_callback(handle, "/purple/proxy/port", proxy_pref_cb,
2521 NULL);
2522 purple_prefs_connect_callback(handle, "/purple/proxy/username",
2523 proxy_pref_cb, NULL);
2524 purple_prefs_connect_callback(handle, "/purple/proxy/password",
2525 proxy_pref_cb, NULL);
2527 /* Load the initial proxy settings */
2528 purple_prefs_trigger_callback("/purple/proxy/type");
2529 purple_prefs_trigger_callback("/purple/proxy/host");
2530 purple_prefs_trigger_callback("/purple/proxy/port");
2531 purple_prefs_trigger_callback("/purple/proxy/username");
2532 purple_prefs_trigger_callback("/purple/proxy/password");
2535 void
2536 purple_proxy_uninit(void)
2538 while (handles != NULL)
2540 purple_proxy_connect_data_disconnect(handles->data, NULL);
2541 purple_proxy_connect_data_destroy(handles->data);
2544 purple_prefs_disconnect_by_handle(purple_proxy_get_handle());
2546 purple_proxy_info_destroy(global_proxy_info);
2547 global_proxy_info = NULL;